Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Сессии и cookie

HTTP не помнит предыдущих запросов: каждый приходит на сервер сам по себе. Чтобы помнить пользователя между страницами, используют сессию — набор данных, привязанный к посетителю. Проще всего хранить сессию прямо в cookie: React Router подписывает её секретом, поэтому подделать содержимое нельзя.

Хранилище сессий

Создайте файл app/sessions.server.ts:

// app/sessions.server.ts
import { createCookieSessionStorage } from "react-router";
 
type SessionData = { userId: string };
type SessionFlashData = { message: string };
 
export const { getSession, commitSession, destroySession } =
  createCookieSessionStorage<SessionData, SessionFlashData>({
    cookie: {
      name: "__session",
      httpOnly: true,
      path: "/",
      sameSite: "lax",
      maxAge: 60 * 60 * 24 * 7,
      secrets: [process.env.SESSION_SECRET ?? "dev-secret"],
      secure: process.env.NODE_ENV === "production",
    },
  });

Функция возвращает три помощника: getSession читает сессию из заголовка Cookie, commitSession превращает её в заголовок Set-Cookie, destroySession удаляет. Тип SessionData описывает постоянные данные, SessionFlashData — одноразовые.

Флеш-сообщение

Флеш-сообщение живёт до первого чтения. Удобно показать «Статья опубликована» после редиректа. Запишите его в действии app/routes/new-post.tsx:

// app/routes/new-post.tsx
import { commitSession, getSession } from "../sessions.server";
 
// в конце action, вместо прежнего return:
const session = await getSession(request.headers.get("Cookie"));
session.flash("message", "Статья опубликована");
 
return redirect(`/posts/${post.id}`, {
  headers: { "Set-Cookie": await commitSession(session) },
});

Сессия изменена на сервере, а заголовок Set-Cookie отправляет её браузеру.

Чтение в макете

Загрузчик макета app/routes/layout.tsx достаёт сообщение и снова сохраняет сессию — уже без него:

// app/routes/layout.tsx
import { Outlet, data } from "react-router";
import type { Route } from "./+types/layout";
import { commitSession, getSession } from "../sessions.server";
 
export async function loader({ request }: Route.LoaderArgs) {
  const session = await getSession(request.headers.get("Cookie"));
  const message = session.get("message") ?? null;
 
  return data(
    { message },
    { headers: { "Set-Cookie": await commitSession(session) } },
  );
}
 
export default function Layout({ loaderData }: Route.ComponentProps) {
  return (
    <>
      {loaderData.message && <p className="flash">{loaderData.message}</p>}
      <Outlet />
    </>
  );
}

Опубликуйте статью — над ней появится «Статья опубликована». Обновите страницу — сообщение исчезнет.

Методы сессии

МетодЧто делает
session.get(ключ)Читает значение
session.set(ключ, значение)Записывает
session.flash(ключ, значение)Записывает до первого чтения
session.unset(ключ)Удаляет одно значение
session.has(ключ)Проверяет наличие

Частая ошибка

Изменить сессию и забыть вернуть заголовок Set-Cookie. Объект session существует только на время запроса: без commitSession в ответе браузер не узнает об изменениях, и при следующем запросе всё будет как раньше. Любой set, flash или unset должен заканчиваться ответом с этим заголовком.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.