on this page
- The whole idea in one sentence
- What “a static build” actually means
- The plot twist: a single-page app is still just files
- When you genuinely need a server
- And then there’s the database
- The map: what it is, where it goes
- Frontend and backend are two different deploys
- Common mistakes
- So, what are you deploying?
You finished the thing. It runs. You open the deploy dashboard, ready to click the big button, and then you hit a wall of choices: static, serverless, edge, functions, web service, container. Pick one. No pressure.
The reason that menu feels impossible is that nobody told you the one thing it hinges on: what you’re deploying is not all the same kind of thing. An Astro blog and an Express API look similar in your editor (folders, files, npm scripts) but they want completely different homes. Put them in the wrong one and you get a green checkmark followed by a site that does nothing, or a build that fails for reasons that read like a ransom note.
So before you pick a host, let’s figure out what you’re actually holding. There are really only two shapes, and almost everything is built out of them.
The whole idea in one sentence
A static build is just files. A server is a program that has to keep running.
That’s the fork in the road, and every hosting decision flows from it. Files are easy to hand out: copy them onto a hundred machines around the world and let anyone download them. A running program is harder, because something has to keep that program alive, give it memory, and wake it up every time a request shows up.
Hosts that are great at the first job (handing out files fast) are usually bad at the second (babysitting a long-lived process), and the other way around. Keep that one sentence in your head and the rest of this is just sorting your project into the right bucket.
What “a static build” actually means
Take an Astro blog. You write components and Markdown, you run npm run build (npm is the Node Package Manager, the tool that installs and runs your project’s stuff), and out the other end comes a folder full of plain index.html, .css, and .js files. Open one in your browser straight off your hard drive and it works. No program is running on the server. There’s nothing to run.
npm run build
# spits out a dist/ folder of plain files
ls dist
# index.html about/index.html _astro/ favicon.svg ...
Because it’s only files, any machine that can serve files can serve your site, which is why static hosting is basically free and stupidly fast. The host copies your dist/ onto a CDN (a Content Delivery Network, a fleet of servers spread across the globe) and every visitor gets the copy nearest to them. There’s no thinking happening per request, just a file getting handed over.
This is the easy case. A portfolio, a docs site, a marketing page, most blogs: static builds, all of them.
The plot twist: a single-page app is still just files
Here’s where people tie themselves in knots. A React SPA (Single-Page Application) or an Angular app feels heavier and more “appy” than a blog. It has routing, state, a login screen, live data. Surely that needs a server?
It does not. When you build a React or Angular app, you also get a folder of static files: one index.html, a pile of JavaScript, some CSS. The host hands those out exactly like it hands out the blog. The difference is what happens after they land in the browser. The JavaScript boots up, draws the interface, and then makes its own requests for data, calling an API somewhere out on the internet. All the dynamic feeling lives in the browser and in that API, not in how the files got delivered.
So a React SPA and an Astro blog deploy to the same kind of place. The data the SPA fetches lives on a server, sure, but that server is a separate thing you deploy separately. We’ll get to it.
When you genuinely need a server
Now the other shape. A server is a program that starts up and then sits there, awake, answering requests for as long as you keep it alive. It is not a folder of files. It’s a process with a heartbeat.
Two common reasons you end up with one.
First, SSR (Server-Side Rendering). A Next.js app in SSR mode runs your code on a server for each request, builds the HTML fresh right then, and sends it down. The visitor gets a finished page instead of a blank shell that fills itself in. That’s genuinely useful (faster first paint, better for search engines), but it means something has to execute your code on every single hit. Files can’t do that. A running program has to.
Second, a backend API. An Express server (Express is the most common web framework for Node) is a program whose entire job is to wait for requests and answer them: log a user in, save a post, return a list of orders. It runs app.listen(3000) and then it stays listening. Turn it off and the API is gone.
// the line that defines a server: it stays running
app.listen(3000, () => {
console.log("API awake, waiting for requests");
});
Both of these need a host that will keep a process alive around the clock. That rules out the file-only static hosts. You want something like
And then there’s the database
A database like
DATABASE_URL="postgresql://user:password@host:5432/mydb"
The database has to be hosted somewhere that keeps it running and keeps your data safe between restarts (the fancy word is “persistent”, meaning it doesn’t forget everything when the process stops). The same server hosts (Railway, Render, Fly, plus managed options like Neon or Supabase) will run a Postgres instance for you and hand you that connection string. Your job is to feed it to the API as an environment variable and never, ever paste it into your frontend code.
The map: what it is, where it goes
Here’s every example app laid out. Read the middle column first; it decides the third one.
| Your app | What it really is | Where it deploys |
|---|---|---|
| Astro blog | A static build: plain HTML, CSS, JS files | |
| React or Angular SPA | Still just static files, plus JS that calls an API after it loads | Cloudflare Pages, Vercel, any CDN |
| Next.js app (SSR) | A program that runs code on a server per request | Vercel, Railway, Render, Fly.io |
| Express API | A program that keeps running and answers requests | Railway, Render, Fly.io, a VPS |
| Full-stack app | Two jobs: a static (or SSR) frontend plus a backend API | Frontend on a CDN, backend on Railway / Render |
| PostgreSQL database | A separate running program that stores your data | Railway, Render, Fly.io, Neon, Supabase |
Notice the bottom three rows all point at the same family of hosts, and the top two point at the other family. That’s the one-sentence rule doing all the work: files go on CDNs, running programs go on process hosts.
Frontend and backend are two different deploys
The big unlock, especially for a full-stack app: “deploying the frontend” and “deploying the backend” are two separate jobs, and they very often happen on two different services.
A typical real project splits like this. The React frontend is a static build, so it lives on Cloudflare Pages, fast and free. The Express API is a running program, so it lives on Railway. The Postgres database is another running program, so it also lives on Railway, next to the API. Three pieces, two hosts, one app. The frontend’s JavaScript calls the API’s public URL, the API talks to the database over its connection string, and the user never sees any of the seams.
This feels like more moving parts than you expected, and it is. But each piece is now on a host that’s actually good at running that kind of thing, instead of one host pretending to do everything badly. You push your code to
Common mistakes
Almost every “why won’t this deploy” night ends at one of these. None of them are really your fault; the tooling just doesn’t warn you when you’ve reached for the wrong kind of host.
Trying to put an Express server on a static host. You point Cloudflare Pages or GitHub Pages at your Express repo, the build “succeeds”, and then nothing answers. A static host has nowhere to keep app.listen() running; it only knows how to hand out files. No process means no API. You can’t config your way out of this one, because you’ve got the wrong kind of host. Move the server to Railway, Render, or Fly.
Assuming a React or Angular SPA needs a Node server to be served. People spin up an Express app just to send their built index.html to visitors, then deploy that and pay for a running process they didn’t need. The build output is plain static files. Drop it straight on a CDN. You only need a server for the API the SPA calls, not for delivering the SPA itself.
Forgetting the database needs its own home and connection string. The API runs fine on your laptop because Postgres is also running on your laptop. You deploy the API, it boots, and it instantly crashes trying to reach a database that exists nowhere in the cloud. The database is a separate program you have to host separately, and then you have to hand its connection string to the API as an environment variable. No database, or no connection string, means an API that starts and dies on the first query.
So, what are you deploying?
Run any project through one question: is this a folder of files, or a program that has to keep running? A blog or an SPA is files, so it goes on a CDN. An SSR app, an API, or a database is a running program, so it goes on a host that keeps processes alive. A full-stack app is both, split across two homes that each do their job well.
That’s the whole model. The next time the deploy dashboard throws its wall of options at you, you won’t be guessing, because you’ll already know which shape you’re holding before you ever click a button. Now go ship the thing.