all posts

The best Remix and React Router hosting platforms in 2026

Ajay Kumar··10 min read

Every other React framework lets you postpone the hosting question. Build to static files, drop them on a CDN, and worry about servers when you need them. Remix does not offer that escape hatch, and it never pretended to. Routes have loaders, loaders run per request, and the output of a Remix build is a client bundle plus a server you have to actually run.

That makes Remix unusually honest about hosting, and it also means a Remix deploy exposes properties of your platform that a static site would have hidden — whether responses are streamed or buffered, whether the server process stays alive between requests, how long a request may take before something upstream gives up. This is a buyer's guide organised around those properties rather than around feature grids.

A naming note, because it affects what you search for. Remix v2's framework features were folded into React Router v7, so a modern "Remix app" is often literally a React Router app: same loaders, same actions, same nested routes, different package names and a `react-router-serve` binary instead of `remix-serve`. Everything in this guide applies to both — the runtime shape is identical.

What you are actually deploying

A Remix or React Router build produces two directories. `build/client` holds hashed static assets that should be served by a CDN with long cache lifetimes. `build/server` holds a request handler — a JavaScript module that takes a request and returns a response — wrapped by whatever adapter you chose at build time.

The adapter is the decision that constrains the rest. The Node adapter gives you an ordinary long-lived HTTP server, which runs anywhere Node runs. The Cloudflare adapter targets Workers, which means a different runtime with a different standard library and no filesystem. There are Deno and Bun paths too. All of them serve the same routes; none of them are interchangeable after the fact, because your server code will quietly grow dependencies on whichever runtime you picked.

# What a Remix / React Router v7 build gives you
npm run build
# build/client/   -> hashed assets, cache these forever, serve from a CDN
# build/server/   -> the request handler your platform has to run

# The default production server. It is a real HTTP server: it binds a
# port, it stays alive, and it handles many requests per process.
react-router-serve ./build/server/index.js     # React Router v7
remix-serve ./build/server/index.js            # Remix v2

# Two things every platform needs from you:
#   PORT  -- bind to the port the platform gives you, do not hardcode 3000
#   HOST  -- bind 0.0.0.0, not localhost, or health checks never connect
The single most common Remix deploy failure is binding to localhost. A server listening on 127.0.0.1 works perfectly on your laptop and is unreachable from anything outside the container or VM, so the platform's health check fails and the deploy is marked broken while your logs cheerfully say the server started. Bind 0.0.0.0 and read `process.env.PORT`.

Streaming SSR is where platforms differ most

Remix's most distinctive runtime feature is streaming: a route can return a response immediately, with slow data arriving later inside the same HTTP response while the browser renders the shell. Done well, it turns a slow API dependency into a progressive page instead of a spinner.

Done on the wrong platform, it does nothing at all. Any proxy between your server and the user that buffers the response body will hold the whole thing until it is complete and then send it in one piece — which is functionally identical to not streaming, except you also paid the complexity cost. The symptom is subtle: the page works, the numbers do not improve, and nothing logs an error.

So test it rather than trusting a feature list. Deploy a route that flushes a shell and then resolves a deliberately slow promise, and watch with curl whether the first bytes arrive early. If they do not, the platform is buffering, and no amount of Suspense boundaries will change that.

# Does your platform actually stream? Ask it.
# -N disables curl's own buffering; -w prints time-to-first-byte.
curl -N -o /dev/null -s \
  -w 'first byte: %{time_starttransfer}s   total: %{time_total}s\n' \
  https://your-app.example.com/slow-route

# Streaming works:      first byte 0.14s   total 2.10s
# Proxy is buffering:    first byte 2.08s   total 2.10s
#
# If those two numbers are nearly identical on a route with a slow
# await, your Suspense boundaries are decoration.

Per-request SSR on functions: the cost is real but not fatal

Because there is no static tier, every page view in a Remix app costs a server invocation. On a long-lived Node process this is a non-event — the process is already warm, the module graph is already loaded, and a render is a few milliseconds of work. On a function platform, you are paying the platform's cold-start distribution on the page-load path, and Remix apps hit it more often than Next.js apps do simply because more of their routes are dynamic.

This is survivable and lots of people ship it. What is worth knowing is where it stops being survivable: anything you would normally keep in process memory. An in-memory cache, a rate limiter, a WebSocket connection, a database connection pool. In a long-lived server those are trivial. On functions each of them needs an external service, and the fifth one is the point where people usually decide the server was simpler.

Sessions, secrets, and the environment split

Remix's cookie sessions are signed with a secret that must be identical across every instance of your server, and stable across deploys. Generating it at startup — which is a surprisingly popular accident — logs every user out on each deploy and breaks entirely the moment you run two instances. Put it in the platform's secret store and read it at runtime.

The other classic is the build-time versus runtime environment split. Anything referenced in client code is baked into the bundle at build time and is public forever; anything read inside a loader or action is read at runtime on the server and stays private. Platforms differ in whether their environment variables are available during the build, at runtime, or both, and getting this wrong is how database URLs end up in a client bundle.

The platforms

  • Vercel — First-class support with a Remix/React Router preset, excellent CDN for the client bundle, and preview deployments that are genuinely good. Your server runs as functions, so read the section above about in-process state, and check the maximum request duration against your slowest loader.
  • Netlify — Similar shape: strong CDN and deploy previews, server routes as functions. A well-trodden path for content-heavy Remix sites.
  • Cloudflare Workers — The lowest-latency option and the most constrained. You commit to the Workers runtime at build time, which means Workers-compatible libraries only and no Node filesystem. Pairs naturally with D1, KV, and R2; painful if your data layer assumes a TCP Postgres connection without a proxy.
  • Fly.io — Runs the Node adapter as an ordinary long-lived process on a real VM, close to your users, with WebSockets and background work behaving normally. Best when you want the Node server and control over where it runs.
  • Render — Build command, start command, done. Long-running web service, managed Postgres next door, workers and cron as first-class citizens. The least surprising way to host the Node adapter.
  • Railway — The fastest git-to-URL path for a Remix app with a database, with per-branch environments that are easy to reason about.
  • Koyeb — Long-running services with scale-to-zero available, which suits a low-traffic Remix app that should not bill for an idle night.
  • Deno Deploy — A good fit specifically if you chose the Deno adapter, with Web-standard APIs throughout. Verify library compatibility before committing.
  • VPS plus a reverse proxy — Node under systemd behind Caddy or nginx. Cheapest, most control, and you own TLS and patching. Turn off response buffering in the proxy or you lose streaming.
  • PandaStack — Git-driven with no Dockerfile: it detects the Vite-based build, runs it, and starts the Node server, with `PORT` and `HOST` injected. Each app is a Firecracker microVM with its own kernel and 4 GiB of RAM, so the build and the server share a real machine; the server is long-lived, so in-memory caches and WebSockets work as written. Scale-to-zero is snapshot-restore rather than a cold container boot, which makes it usable for preview environments. Best for per-branch environments and per-app isolation; not the pick if you want a global edge network in front of your SSR.

Pick by situation

  • Marketing or content site, mostly reads, global audience → Vercel, Netlify, or Cloudflare Workers. The CDN does the heavy lifting and per-request SSR is cheap.
  • An app with a Postgres database and real session state → a long-running Node server: Render, Railway, Fly.io, or PandaStack. Skip the pooler-shaped problems entirely.
  • You want the lowest possible time-to-first-byte worldwide and will adapt your code to get it → Cloudflare Workers, with the data layer chosen to match.
  • You hold WebSockets, run background jobs, or keep anything in process memory → anything long-running. Not functions.
  • Preview environment for every pull request, and it should cost nothing overnight → PandaStack, mine, or Koyeb for the idle economics; Vercel or Netlify if you want previews with zero setup and don't mind the function model.
  • You are on the Cloudflare adapter and hitting library walls → this is the moment to reconsider the Node adapter rather than to keep shimming. The adapter is a build-time decision but it is not a life sentence.

The short version

Pick the adapter first and the platform second, because the adapter is what constrains your code. Node if you want portability and in-process state; Workers if latency is the product and you will live inside the runtime's rules.

Then verify two things on any host before you commit: that it does not buffer your streamed responses, and that it makes environment variables available at the phase you need them. Those two properties cause more Remix deploy pain than everything else combined, and neither one is in a feature comparison table. Get them right and Vercel, Netlify, Cloudflare, Fly, Render, Railway, Koyeb, a VPS, and PandaStack will all serve your app well.

Frequently asked questions

Can I deploy Remix as a static site?

Not in the general case, and this is by design rather than an omission. Remix's data model is loaders that run on the server for every request, so there is no build step that can produce finished HTML for a route whose data depends on the request — a cookie, a session, a query parameter, the current time. You can get close for a genuinely static subset by prerendering routes whose data never varies, and React Router v7 has a prerender option for exactly that, but a Remix app with authentication or personalised content needs a server running somewhere. If your whole site is static, a static framework will be a better fit than fighting Remix into that shape.

Why is my Remix streaming not working in production?

Almost always a buffering proxy between your server and the browser. Streaming SSR only works if every hop forwards bytes as they arrive; a single layer that waits for the complete response body collapses the whole thing back into a normal render, and nothing logs an error because technically everything worked. Test it directly: deploy a route that flushes a shell then awaits a deliberately slow promise, and measure with curl -N and -w 'time_starttransfer'. If time-to-first-byte is nearly identical to total time, something is buffering. On your own nginx that is proxy_buffering; on a managed platform it is usually a property of their edge you cannot change, so the fix is choosing a platform that streams. Check compression middleware too — some configurations buffer to compress.

Is Remix or React Router v7 the right package to use now?

For a new project, React Router v7 in framework mode: Remix v2's framework capabilities were folded into it, so it is the actively developed line, and the concepts transfer one to one — loaders, actions, nested routes, the same build shape of a client bundle plus a server handler. Existing Remix v2 apps are not urgent to migrate; the official upgrade path is mostly import renames and a change from remix-serve to react-router-serve. From a hosting perspective the distinction does not matter at all: both produce build/client and build/server, both need a long-lived process or a function to run the handler, and every platform note in this guide applies identically.

Do I need a CDN in front of a Remix app?

You want one for build/client and you may not need one for the server. The client build is hashed, immutable, and cacheable forever, and serving those files from your origin on every page load is wasted latency and wasted bandwidth. Most platforms handle this automatically. The server side is a different question and depends on your data: HTML from a personalised loader is not cacheable at all, while HTML from a loader reading public content might be, using ordinary Cache-Control headers that a CDN respects. Start by making sure assets are cached and served near your users, then treat server-response caching as an optimisation you measure rather than a default you assume.

How do I run background jobs in a Remix app?

Not inside a loader or an action, whichever platform you are on. A loader's job is to return data for a render; work started there and not awaited either gets killed when the response finishes or leaks. On a long-lived Node server you have real options: a separate worker process consuming a queue, which is the version that scales, or an in-process scheduler for genuinely small periodic work. On functions you have fewer: the environment is frozen between invocations, so anything not finished when you return the response may simply stop, and you need the platform's queue or cron primitive plus a separate handler. If you know you have background work, that is a strong argument for a long-running server host over a function host, and worth deciding before you build the queue.

Keep reading

Run code in a microVM in one API call.

49ms p50 cold start. Fork, snapshot, and scale to zero.

Start free
Written by Ajay Kumar, Founder, PandaStack.