Documentation Index
Fetch the complete documentation index at: https://mintlify.com/remix-run/react-router/llms.txt
Use this file to discover all available pages before exploring further.
Session Management
Learn how to manage user sessions in React Router applications using the Session API.
Overview
React Router provides a powerful session management system that stores session data server-side while keeping a session ID in a cookie. Sessions are ideal for authentication, user preferences, and temporary data.
Creating a Session Storage
Create a session storage backend:
// app/sessions.ts
import { createCookieSessionStorage } from "react-router";
export const { getSession, commitSession, destroySession } =
createCookieSessionStorage({
cookie: {
name: "__session",
secrets: [process.env.SESSION_SECRET],
sameSite: "lax",
path: "/",
httpOnly: true,
secure: process.env.NODE_ENV === "production",
maxAge: 60 * 60 * 24 * 7, // 1 week
},
});
Reading Session Data
Access session data in loaders and actions:
import { redirect } from "react-router";
import { getSession } from "~/sessions";
import type { Route } from "./+types/dashboard";
export async function loader({ request }: Route.LoaderArgs) {
const session = await getSession(request.headers.get("Cookie"));
const userId = session.get("userId");
if (!userId) {
return redirect("/login");
}
const user = await getUser(userId);
return { user };
}
Setting Session Data
Store data in the session:
import { redirect } from "react-router";
import { getSession, commitSession } from "~/sessions";
import type { Route } from "./+types/login";
export async function action({ request }: Route.ActionArgs) {
const formData = await request.formData();
const email = formData.get("email");
const password = formData.get("password");
const user = await verifyLogin(email, password);
if (!user) {
return { error: "Invalid credentials" };
}
const session = await getSession(request.headers.get("Cookie"));
session.set("userId", user.id);
return redirect("/dashboard", {
headers: {
"Set-Cookie": await commitSession(session),
},
});
}
Destroying Sessions
Log users out by destroying their session:
import { redirect } from "react-router";
import { getSession, destroySession } from "~/sessions";
import type { Route } from "./+types/logout";
export async function action({ request }: Route.ActionArgs) {
const session = await getSession(request.headers.get("Cookie"));
return redirect("/", {
headers: {
"Set-Cookie": await destroySession(session),
},
});
}
Flash Messages
Store one-time messages that survive redirects:
import { redirect } from "react-router";
import { getSession, commitSession } from "~/sessions";
import type { Route } from "./+types/delete-post";
export async function action({ request, params }: Route.ActionArgs) {
await deletePost(params.id);
const session = await getSession(request.headers.get("Cookie"));
session.flash("message", "Post deleted successfully");
return redirect("/posts", {
headers: {
"Set-Cookie": await commitSession(session),
},
});
}
// Display flash message
export async function loader({ request }: Route.LoaderArgs) {
const session = await getSession(request.headers.get("Cookie"));
const message = session.get("message"); // Automatically removed after reading
return json(
{ message },
{
headers: {
"Set-Cookie": await commitSession(session),
},
}
);
}
export default function Posts({ loaderData }: Route.ComponentProps) {
return (
<div>
{loaderData.message && (
<div className="flash-message">{loaderData.message}</div>
)}
{/* Posts list */}
</div>
);
}
Database Session Storage
Store sessions in a database for persistence:
// app/sessions.server.ts
import { createSessionStorage } from "react-router";
import { db } from "~/db.server";
export const { getSession, commitSession, destroySession } =
createSessionStorage({
cookie: {
name: "__session",
secrets: [process.env.SESSION_SECRET],
sameSite: "lax",
httpOnly: true,
secure: process.env.NODE_ENV === "production",
},
async createData(data, expires) {
const session = await db.session.create({
data: {
data: JSON.stringify(data),
expiresAt: expires,
},
});
return session.id;
},
async readData(id) {
const session = await db.session.findUnique({ where: { id } });
if (!session || session.expiresAt < new Date()) {
return null;
}
return JSON.parse(session.data);
},
async updateData(id, data, expires) {
await db.session.update({
where: { id },
data: {
data: JSON.stringify(data),
expiresAt: expires,
},
});
},
async deleteData(id) {
await db.session.delete({ where: { id } });
},
});
Session Utilities
Create helper functions for common session operations:
// app/session.server.ts
import { redirect } from "react-router";
import { getSession } from "~/sessions";
export async function requireUserId(
request: Request,
redirectTo: string = "/login"
) {
const session = await getSession(request.headers.get("Cookie"));
const userId = session.get("userId");
if (!userId) {
const searchParams = new URLSearchParams([["redirectTo", redirectTo]]);
throw redirect(`/login?${searchParams}`);
}
return userId;
}
export async function requireUser(request: Request) {
const userId = await requireUserId(request);
const user = await getUser(userId);
if (!user) {
throw redirect("/login");
}
return user;
}
export async function requireAdmin(request: Request) {
const user = await requireUser(request);
if (user.role !== "admin") {
throw new Response("Forbidden", { status: 403 });
}
return user;
}
Use in routes:
import { requireUser } from "~/session.server";
import type { Route } from "./+types/dashboard";
export async function loader({ request }: Route.LoaderArgs) {
const user = await requireUser(request);
return { user };
}
Session Timeout
Implement automatic session expiration:
import { redirect } from "react-router";
import { getSession, commitSession } from "~/sessions";
const SESSION_TIMEOUT = 30 * 60 * 1000; // 30 minutes
export async function requireActiveSession(request: Request) {
const session = await getSession(request.headers.get("Cookie"));
const userId = session.get("userId");
const lastActivity = session.get("lastActivity");
if (!userId) {
throw redirect("/login");
}
const now = Date.now();
if (lastActivity && now - lastActivity > SESSION_TIMEOUT) {
throw redirect("/login?timeout=true");
}
// Update last activity
session.set("lastActivity", now);
return {
userId,
headers: {
"Set-Cookie": await commitSession(session),
},
};
}
Remember Me
Implement persistent login:
import { getSession, commitSession } from "~/sessions";
import type { Route } from "./+types/login";
export async function action({ request }: Route.ActionArgs) {
const formData = await request.formData();
const rememberMe = formData.get("rememberMe") === "on";
const user = await verifyLogin(formData);
if (!user) {
return { error: "Invalid credentials" };
}
const session = await getSession(request.headers.get("Cookie"));
session.set("userId", user.id);
const maxAge = rememberMe ? 60 * 60 * 24 * 30 : 60 * 60 * 24; // 30 days or 1 day
return redirect("/dashboard", {
headers: {
"Set-Cookie": await commitSession(session, { maxAge }),
},
});
}
Multiple Sessions
Manage different types of sessions:
// app/sessions.ts
import { createCookieSessionStorage } from "react-router";
// User authentication session
export const userSession = createCookieSessionStorage({
cookie: {
name: "__user_session",
secrets: [process.env.SESSION_SECRET],
httpOnly: true,
secure: true,
maxAge: 60 * 60 * 24 * 7,
},
});
// Admin session with stricter settings
export const adminSession = createCookieSessionStorage({
cookie: {
name: "__admin_session",
secrets: [process.env.ADMIN_SESSION_SECRET],
httpOnly: true,
secure: true,
sameSite: "strict",
maxAge: 60 * 60, // 1 hour
},
});
Best Practices
- Always use secrets - Sign session cookies to prevent tampering
- Set httpOnly - Prevent JavaScript access to session cookies
- Use secure in production - Ensure HTTPS-only transmission
- Implement session timeout - Automatically expire inactive sessions
- Store minimal data - Keep sessions small for performance
- Use flash messages - Great for one-time notifications after redirects
- Consider database storage - Better for high-traffic apps or when you need to invalidate all sessions
- Rotate secrets - Keep multiple secrets for zero-downtime rotation