Skip to main content
The session module (app/sessions.ts) configures and exports React Router’s cookie session storage. It stores the Stripe customerId after registration so that server-side loaders and actions can identify the current user without a database lookup.

Overview

The module calls createCookieSessionStorage from react-router to create an encrypted, signed cookie named __session. The cookie carries two types of data:
  • Session data — persisted across requests: customerId
  • Flash data — available only for the next request, then cleared: error
The three returned functions — getSession, commitSession, and destroySession — are re-exported for use throughout the app.
The default secrets value in the source is ["s3cret1"]. You must replace this with a long, random secret before deploying to production. A compromised secret allows anyone to forge session cookies. Also note that maxAge is set to 60 seconds, which is suitable for development only — increase this value for production use.
The session cookie is configured with the following options:
name
string
default:"__session"
The name of the cookie set in the browser. Firebase Hosting and some CDNs strip cookies other than __session, so this name is chosen for broad compatibility.
httpOnly
boolean
default:"true"
Prevents client-side JavaScript from accessing the cookie. Mitigates XSS-based session theft.
maxAge
number
default:"60"
Cookie lifetime in seconds. The current value of 60 seconds is for development. Set this to a longer duration (e.g. 604800 for 7 days) in production.
path
string
default:"/"
Restricts the cookie to this URL path prefix. / makes it available on all routes.
sameSite
string
default:"lax"
Controls cross-site request behavior. lax allows the cookie to be sent on top-level navigations, protecting against CSRF while keeping normal link navigation working.
secrets
string[]
required
An array of secrets used to sign and verify the cookie. React Router uses the first secret to sign new cookies and accepts all secrets for verification, enabling zero-downtime secret rotation. Replace ["s3cret1"] with a cryptographically random value.
secure
boolean
default:"true"
Restricts the cookie to HTTPS connections. Set to false only in local development if you are not using HTTPS.

Session data shape

type SessionData = {
  customerId: string;
};
customerId holds the Stripe customer ID (e.g. cus_Abc123) returned by createCustomer at registration. Every route that needs to call a Stripe API reads this value from the session.

Flash data shape

type SessionFlashData = {
  error: string;
};
Flash data is automatically removed from the session after it is read once. The error key is used to surface server-side error messages — for example, when a payment fails — across a redirect boundary.

Exported functions

All three functions are created by createCookieSessionStorage and re-exported directly:
getSession
function
required
Reads and deserializes the session from the Cookie request header. Returns a Session object. Always call this at the top of a loader or action that needs session data.
commitSession
function
required
Serializes the session back into a signed cookie string. Use the return value as the Set-Cookie response header to persist any changes (including new flash messages) to the client.
destroySession
function
required
Invalidates the session cookie by setting it to expire immediately. Call this in a logout action to clear the customer’s session.

Usage pattern

The following examples show how to read and write session data in a React Router loader and action. Reading the session in a loader
account loader
import { getSession } from "~/sessions";

export async function loader({ request }: Route.LoaderArgs) {
  const session = await getSession(request.headers.get("Cookie"));
  const customerId = session.get("customerId");

  if (!customerId) {
    // No session — redirect to registration
    return redirect("/");
  }

  return { customerId };
}
Writing the session in an action
register action
import { getSession, commitSession } from "~/sessions";
import { createCustomer } from "~/services/customer";

export async function action({ request }: Route.ActionArgs) {
  const formData = await request.formData();
  const customer = await createCustomer(
    String(formData.get("email")),
    String(formData.get("name"))
  );

  const session = await getSession(request.headers.get("Cookie"));
  session.set("customerId", customer.id);

  return redirect("/prices", {
    headers: { "Set-Cookie": await commitSession(session) },
  });
}
Flashing an error across a redirect
error flash
import { getSession, commitSession } from "~/sessions";

// In an action that catches a Stripe error:
const session = await getSession(request.headers.get("Cookie"));
session.flash("error", "Your card was declined. Please try a different card.");

return redirect("/subscribe", {
  headers: { "Set-Cookie": await commitSession(session) },
});

// In the next loader, read and clear the flash message:
const session = await getSession(request.headers.get("Cookie"));
const error = session.get("error"); // undefined on the subsequent request
Destroying the session on logout
logout action
import { getSession, destroySession } from "~/sessions";

export async function action({ request }: Route.ActionArgs) {
  const session = await getSession(request.headers.get("Cookie"));
  return redirect("/", {
    headers: { "Set-Cookie": await destroySession(session) },
  });
}
Always pass the raw Cookie header string — request.headers.get("Cookie") — to getSession. Passing null is safe and returns an empty session, which is useful when no cookie is present on the first visit.

Build docs developers (and LLMs) love