Astro has become a favourite for content-heavy sites: marketing sites, blogs, documentation and online magazines. It ships very little JavaScript by default, so pages load quickly, and it lets you use React, Vue, Svelte or plain HTML components where you need interactivity. Hosting it depends on one choice: is your site fully static, or does it need server rendering for some pages? This guide explains both, then walks through deploying a server-rendered Astro site as a Node.js app on South African servers.
Static or server-rendered?
By default, Astro builds every page to static HTML at build time. That output is a folder of files you can host almost anywhere.
You need server rendering (SSR, which Astro calls "on-demand rendering") when a page must be built for each request, for example:
- pages that show data that changes constantly, or differs per visitor;
- login areas and dashboards that read cookies or sessions;
- form handlers and API endpoints written as Astro endpoints;
- a large site where building every page in advance takes too long.
Many sites mix both: most pages stay static, and a few are rendered on demand. To render anything on demand, Astro needs an adapter for the server it runs on. For a normal Node.js server, that's @astrojs/node.
Option 1: a fully static Astro site
If no page needs server rendering, run the build and upload the result:
npm run build # runs astro build and writes the site to dist/
Upload the contents of dist/ to your web hosting's document root, or let your Git deploy run the build for you. There is no Node.js process to keep running. Just remember that the site only changes when you rebuild, so content from a CMS appears after the next build.
Option 2: server rendering with the Node adapter
Add the adapter
npx astro add node
This installs @astrojs/node and updates your config. Check that it uses standalone mode, which builds a complete server you start with Node.js:
// astro.config.mjs
import { defineConfig } from "astro/config";
import node from "@astrojs/node";
export default defineConfig({
output: "server", // render on demand by default
adapter: node({ mode: "standalone" }),
});
With output: "server", pages are rendered on demand unless you mark them static with export const prerender = true. If most of your site is static, keep the default static output and mark only the dynamic pages with export const prerender = false instead.
Build and start
npm run build
node ./dist/server/entry.mjs
The build writes the server to dist/server/ and the static assets to dist/client/. The standalone server serves both, so you only need to start one file. It listens on the PORT environment variable when it's set, and on HOST for the address; most Node.js hosts set the port for you.
Make sure your package.json has the scripts your host will run:
{
"scripts": {
"build": "astro build",
"start": "node ./dist/server/entry.mjs"
}
}
Environment variables
Astro reads environment variables in two ways, and mixing them up is a common source of bugs:
- Build-time values read with
import.meta.envare replaced into the code when you build. Changing them later has no effect until you rebuild. - Runtime values are read when the server is running. With the Node adapter, server code can read
process.env, and Astro'sastro:envmodule lets you declare which variables are server-only secrets and which are public.
Only variables prefixed with PUBLIC_ are exposed to browser code. Keep API keys and database passwords server-only. The same build-versus-runtime distinction applies to Next.js; our guide to environment variables in production explains it in more depth.
Deploying from GitHub
A Git-based workflow keeps deploys repeatable:
- Push your Astro project to GitHub, with
package-lock.jsoncommitted. - Create a Node.js app on your host, connect the repository and branch.
- Set the build command to
npm run buildand the start file todist/server/entry.mjs. - Add environment variables in the host's dashboard.
- Push. Each push installs dependencies, builds and restarts the app.
Our walkthrough of deploying a Next.js app from GitHub shows the same flow step by step, and the deploy troubleshooting checklist covers the errors you're most likely to see.
Production checklist for Astro
- Set
siteinastro.config.mjsto your canonical URL, so canonical tags and the sitemap use the right domain. - Add
@astrojs/sitemapand arobots.txt. See our sitemap and robots.txt guide for what to include. - Optimise images with Astro's
<Image />component, which generates sized, modern formats at build time for static pages. - Check that on-demand pages send sensible
Cache-Controlheaders. - Redirect HTTP to HTTPS and pick
wwwor non-www. - Test the production build locally with
npm run buildandnpm startbefore you deploy.
Why host Astro in South Africa?
Astro already makes pages light. Hosting them close to your visitors cuts the network round trip too, which matters most for server-rendered pages, where every request waits for the server. If your audience is in South Africa, a local server and a database in the same data centre keep each request short. Our guide to website speed in South Africa explains where the time goes.
Frequently asked questions
Does Astro need Node.js on the server?
Only for pages rendered on demand. A fully static Astro site is plain files and needs no Node.js process.
Can I use Astro with a headless CMS?
Yes. Static pages fetch content at build time, so trigger a rebuild when content changes; on-demand pages fetch it on every request.
Which Node.js version should I use?
Use a version supported by your Astro release; Astro's documentation lists the minimum. Use the same major version locally and on the server.
Can I run Astro and an API in the same app?
Yes. Astro endpoints in src/pages can handle API requests, and they run in the same standalone server.
Ready to deploy? NewHost Node.js hosting runs server-rendered Astro on South African servers: connect GitHub, set the start file to dist/server/entry.mjs, and every push builds and deploys, with free SSL on your domain. Fully static? Our web hosting plans work too.