ssrchecker
HomeCheckFrameworksRemix

Is your Remix app server-rendered? (By default, yes)

Short answer
Remix runs a server loader for every route by default and renders the page to complete HTML with that data before sending it, so most Remix routes return full content to crawlers. Content goes missing when a route uses a clientLoader to fetch its primary data in the browser, when key content is deferred and streamed, or when the app is built in SPA Mode.

Remix is server-first by design — the loader model is the whole point of the framework — so it belongs with Astro and Gatsby on the reassuring side of this set, not with the client-first SPAs. The Remix diagnosis is about a few specific opt-outs from that default, each tied to Remix's loader and nested-routing model.

Check your Remix route
https://

Why loaders make Remix safe by default

Every Remix route can export a loader, a function that runs on the server before the route renders. Remix calls the loaders for the matched route and its parents, resolves their data, renders the route tree to HTML with that data in place, and sends the finished page. Your component reads it through useLoaderData. By the time the HTML reaches the browser — or a crawler — the content is already in it.

This is why a well-built Remix route returns a near-100% content ratio with metadata intact before any JavaScript runs. The failures below are all cases where data deliberately does not come from a server loader.

The clientLoader trap

Remix added clientLoader for data that should be fetched in the browser — client-side caches, values that depend on browser-only state, or navigations you want to speed up after the first load. A clientLoader runs in the browser, not on the server, so anything it returns is not in the initial HTML.

The trap has two grades. If a route has both a server loader and a clientLoader, the server data is still in the HTML and only the client-fetched additions are missing — a HYBRID result. But if a route is defined with only a clientLoader and no server loader, that route has no server-resolved data at all: its content is assembled entirely in the browser, and a crawler gets a shell. That is a full CSR verdict on an otherwise server-first framework. Keep primary, indexable content in the server loader.

Deferred streaming and <Await>

Remix can stream. Wrapping a value in defer() lets the route respond immediately with the rest of the page while that value resolves, rendering a <Suspense> fallback in its place and streaming the real value in through <Await>once it's ready.

What the crawler sees
The initial HTML contains the fallback, not the deferred value. Whether that matters depends entirely on whatyou defer. Deferring a below-the-fold "related items" strip is fine — that's what streaming is for. Deferring the article body or product description strands your primary content behind a fallback. Defer extras, never the main content.

Nested routes render in parts

Remix's routing is nested: a URL is composed of a parent route and its children, each with its own loader. This is a strength, but it means a single URL can be partly server-rendered and partly not. A parent layout route with a proper server loader can render perfectly while a child route fetches its section client-side — so the page "works" and still hands a crawler a page with a hole in it where the child's content should be. When a Remix route reads HYBRID, check each route in the nesting chain, not just the leaf.

Side-by-side comparison of a Remix route's raw HTML with loader content present versus a nested child route section that is missing until JavaScript runs
One URL, two behaviors: parent server-rendered, child section client-fetched.

SPA Mode ships an empty shell

Remix can be built in SPA Mode, which turns off server rendering entirely and produces a single-page app: an empty HTML shell hydrated and rendered in the browser, exactly like a plain React SPA. It exists for cases where a server isn't available or wanted. If your Remix app is in SPA Mode, this check will return CSR, and the fix is not a Remix tweak — it is deciding whether that content needs to be indexable at all, and if so, moving off SPA Mode. See the React page for the shape of that problem.

Your configuration and the verdict to expect

Find your setup. On Remix, an unexpected amber or red almost always points to a client loader, a deferred value, or SPA Mode rather than to missing server rendering.

SetupExpectWhy
A route with a server loader returning its dataSSRThe loader runs on the server; data is in the HTML before it's sent. The default good case.
A route using useLoaderData with that server dataSSRThe hook reads server-resolved data already present in the HTML.
A route with a clientLoader that fetches primary contentHYBRID or CSRclientLoader runs in the browser. That content isn't in the initial HTML.
A route defined with ONLY a clientLoaderCSRNo server data for that route. The content is assembled entirely client-side.
defer() with primary content inside <Await>HYBRIDThe Suspense fallback is in the initial HTML; the real value streams after.
defer() used only for below-the-fold extrasSSRPrimary content is in the HTML; only non-critical parts stream in.
A nested child route fetching content client-sideHYBRIDThe parent renders server-side; the child's content arrives after load.
Remix built in SPA ModeCSRSPA Mode ships an empty shell and renders everything client-side, like a plain React app.

When Remix actually breaks

01
A route with only a clientLoader
No server data means a browser-assembled page and a shell for crawlers. Add a server loader for any content that needs to index.
02
Primary content wrapped in defer()
The crawler gets the Suspense fallback, not the streamed value. Defer only non-critical, below-the-fold pieces.
03
A nested child route fetching content client-side
The parent renders fine while the child's section is missing from the HTML. Audit every route in the chain, not just the leaf.
04
The app built in SPA Mode
Server rendering is off entirely; it behaves like a React SPA. This is an architecture decision to revisit, not a config tweak.
05
A JavaScript error before <Scripts> hydrates
If hydration fails, client-dependent sections can be stranded. Server-loaded content still shows, but interactivity and any client-fetched parts don't.

Remix rendering FAQ

Yes. Remix runs a server loader for every matched route, resolves the data, and renders the route tree to complete HTML before sending it. Server rendering is the framework's default behavior, not an opt-in, which is why most Remix routes deliver full content to crawlers without executing JavaScript.