The brief that landed on my desk asked how the new Auth0 SDK handles sessions in React Server Components. Fair question, except for one detail: TanStack Start doesn’t have React Server Components. Not in the Next.js App Router sense — no 'use server' component tree, no streaming RSC payload, no server-only component boundary the framework enforces for you. The authentication story here runs through SSR, isomorphic loaders, and server functions. If you walk in expecting the App Router mental model, you’ll spend an afternoon looking for a boundary that isn’t there.
So let’s throw out the RSC framing and talk about what the SDK actually does, measured in the numbers that decide whether you ship it.
0 React Server Components to reason about
This is the thing to internalise before you write a line of config. TanStack Start’s execution model is server functions (createServerFn), route loaders that run on the server during SSR and on the client during navigation, and plain client components that hydrate in the browser. There is no third “server component” category sitting between them.
That simplifies the auth question rather than complicating it. You have exactly two places a session needs to be legible: server-side execution (loaders, server functions, middleware) and client-side execution (components that have hydrated). The SDK reads the encrypted session cookie on the server and exposes the user to the client through a provider. No token ever has to cross a serialization boundary you didn’t choose.
The Auth0 post reaches for App Router vocabulary because that’s the audience it’s trying to win over. Don’t let it reframe your architecture. The relevant boundary is server function vs. client component, and it’s a cleaner line than anything RSC gives you.
5 environment variables to boot
Initialisation is a single client instance fed by five values. Nothing exotic, but two of them are easy to get wrong.
AUTH0_SECRET is the one people skip past — it’s the key the SDK uses to encrypt the session cookie, not anything Auth0 issues. Generate it with openssl rand -hex 32 and treat it like a signing key, because that’s what it is. Rotate it and every live session dies. APP_BASE_URL has to match what you register in the tenant, or the callback round-trip breaks with an error that blames the redirect URI instead of the base URL.
On the tenant side, register your Allowed Callback URL as {APP_BASE_URL}/api/auth/callback and your Allowed Logout URL as the base URL itself. If you want refresh tokens — and for anything long-lived you do — enable the offline_access scope and turn on refresh token rotation in the application settings. The SDK won’t refresh what Auth0 won’t issue.
1 catch-all route plus 1 middleware mount
Route protection comes down to two wiring steps. First, a catch-all route hands the /api/auth/* paths — login, callback, logout, and the profile endpoint — to the SDK’s handler:
Second, you mount the SDK’s middleware globally so every server request rehydrates the session from the cookie before your loaders run. That’s what makes getSession() cheap inside a loader — the decrypt already happened.
Guarding a route is then a beforeLoad check, which runs on the server during SSR and on the client during client-side navigation. One guard, both execution contexts:
This is the part I actually like. The beforeLoad hook is a better home for an auth gate than Next.js middleware, because it’s colocated with the route it protects and it participates in the router’s type system. No matcher regex in a separate file that drifts out of sync with your route tree. The guard lives next to the thing it guards.
4 KB of cookie, and what it costs you
The default session store is a stateless encrypted cookie. The entire session, including the ID token claims and, if you asked for them, the access and refresh tokens, is AES-encrypted with AUTH0_SECRET and parked in the browser. No server-side session table, no Redis, nothing to scale.
That buys you zero-infrastructure sessions. It also hands you a hard ceiling — and this next number is general browser behaviour, not something in Auth0’s docs: browsers cap cookies near 4 KB. A lean ID token fits with room to spare. Start stuffing a dozen custom claims, a long roles array, and a refresh token into the same cookie and you’ll crowd that limit — at which point the SDK chunks across multiple cookies, which works until a proxy or a CDN trims your headers and the session silently fails to reconstruct. If you’re carrying heavy tokens, plan on a stateful store backed by your own storage rather than riding the cookie to its ceiling.
Token refresh is automatic but only on the server. Call getAccessToken() inside a server function and the SDK checks expiry, uses the refresh token to mint a new one when needed, and rewrites the session cookie in the response. The client never touches a refresh token — correct, and worth stating plainly, because the whole point of the cookie-session design is that the browser never holds a long-lived credential it could leak.
2 ways to read the user, depending on where you stand
Server side, getSession() is your source of truth — in loaders, server functions, and the middleware chain. Client side, you wrap the app once and read the user through a hook:
Login and logout are plain anchor navigations to the SDK’s routes — not fetch calls — because both need a full-document redirect to Auth0 and back. Logout clears the local cookie and bounces through the Auth0 /v2/logout endpoint so the tenant session dies too. If you only clear the cookie and skip the round-trip, the next login is silent and your “log out” button is decorative. The SDK does it right by default; just don’t reimplement it with a fetch and wonder why sessions resurrect.
$0 to add the SDK — the gates are where they always were
The SDK itself costs nothing to install. Auth0 the platform has a free tier that, per the company’s current pricing page, supports real production use rather than just a demo. The exact monthly-active-user ceiling and which features — custom domains, passkeys, social connections — fall inside that free tier change often enough that I won’t pin a number to them here. Check the pricing page rather than trusting a figure in a blog post, including this one. That’s the number that decides whether this is worth evaluating at all, and it’s the one you should verify yourself.
The gates sit where they always have with Auth0, and the TanStack SDK doesn’t move any of them. Organizations (the B2B multi-tenant feature), fine-grained RBAC, and higher MAU tiers live in paid plans. The SDK gives you the OIDC plumbing; it does not unlock any entitlement your tenant doesn’t already have. Check the pricing page before you architect around Organizations, because that decision is a billing decision wearing an engineering costume.
Version 1, and the parity gap that comes with it
This is a first-generation SDK, and it’s worth treating it like one. The API surface may still move between releases, and a day-one framework SDK rarely matches the breadth of a mature sibling that’s had years of edge cases filed against it. I wouldn’t assume every pattern from the more established Next.js SDK transfers one-to-one — and where it doesn’t, the docs may not warn you.
None of that is disqualifying. It’s the normal state of a day-one SDK. But pin your version, read the release notes before you upgrade, and test the flows you actually depend on rather than trusting parity. Most patterns will carry over. The one time one doesn’t will cost you the afternoon.
The honest summary: this is a competent cookie-session SDK wired sensibly into TanStack Start’s router, mislabelled with RSC vocabulary it doesn’t need. Ignore the framing, keep the beforeLoad guard — that part is genuinely nicer than what you left behind.
