Skip to content

Commit 9930beb

Browse files
committed
Improve clarity and consistency of Sessions docs
1 parent 85ce069 commit 9930beb

File tree

2 files changed

+42
-115
lines changed

2 files changed

+42
-115
lines changed

contributors.yml

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
- aaronadamsCA
12
- aaronpowell96
23
- aaronshaf
34
- abereghici

docs/utils/sessions.md

+41-115
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ Remix comes with several pre-built session storage options for common scenarios,
2121

2222
This is an example of a cookie session storage:
2323

24-
```ts filename=app/sessions.ts
25-
// app/sessions.ts
24+
```ts filename=app/sessions.server.ts
25+
// app/sessions.server.ts
2626
import { createCookieSessionStorage } from "@remix-run/node"; // or cloudflare/deno
2727

2828
type SessionData = {
@@ -59,23 +59,23 @@ const { getSession, commitSession, destroySession } =
5959
export { getSession, commitSession, destroySession };
6060
```
6161

62-
We recommend setting up your session storage object in `app/sessions.ts` so all routes that need to access session data can import from the same spot (also, see our [Route Module Constraints][constraints]).
62+
We recommend setting up your session storage object in `app/sessions.server.ts` so all routes that need to access session data can import from the same spot (also, see our [Route Module Constraints][constraints]).
6363

6464
The input/output to a session storage object are HTTP cookies. `getSession()` retrieves the current session from the incoming request's `Cookie` header, and `commitSession()`/`destroySession()` provide the `Set-Cookie` header for the outgoing response.
6565

6666
You'll use methods to get access to sessions in your `loader` and `action` functions.
6767

6868
A login form might look something like this:
6969

70-
```tsx filename=app/routes/login.js lines=[8,11-13,15,20,24,30-32,43,48,53,58]
70+
```tsx filename=app/routes/login.js lines=[8,11-13,15,21-22,27-28,34-36,47-48,53,58,63]
7171
import type {
7272
ActionArgs,
7373
LoaderArgs,
7474
} from "@remix-run/node"; // or cloudflare/deno
7575
import { json, redirect } from "@remix-run/node"; // or cloudflare/deno
7676
import { useLoaderData } from "@remix-run/react";
7777

78-
import { getSession, commitSession } from "../sessions";
78+
import { getSession, commitSession } from "../sessions.server";
7979

8080
export async function loader({ request }: LoaderArgs) {
8181
const session = await getSession(
@@ -87,10 +87,14 @@ export async function loader({ request }: LoaderArgs) {
8787
return redirect("/");
8888
}
8989

90-
const data = { error: session.get("error") };
90+
const data = {
91+
// Read and unset the flash message set by the route action.
92+
error: session.get("error"),
93+
};
9194

9295
return json(data, {
9396
headers: {
97+
// Commit the updated session data.
9498
"Set-Cookie": await commitSession(session),
9599
},
96100
});
@@ -110,6 +114,7 @@ export async function action({ request }: ActionArgs) {
110114
);
111115

112116
if (userId == null) {
117+
// Set a single-use flash message to be read by the route loader.
113118
session.flash("error", "Invalid username/password");
114119

115120
// Redirect back to the login page with errors.
@@ -157,7 +162,7 @@ export default function Login() {
157162
And then a logout form might look something like this:
158163

159164
```tsx
160-
import { getSession, destroySession } from "../sessions";
165+
import { getSession, destroySession } from "../sessions.server";
161166

162167
export const action = async ({ request }: ActionArgs) => {
163168
const session = await getSession(
@@ -187,7 +192,9 @@ export default function LogoutRoute() {
187192

188193
## Session Gotchas
189194

190-
Because of nested routes, multiple loaders can be called to construct a single page. When using `session.flash()` or `session.unset()`, you need to be sure no other loaders in the request are going to want to read that, otherwise you'll get race conditions. Typically if you're using flash, you'll want to have a single loader read it, if another loader wants a flash message, use a different key for that loader.
195+
- Because of nested routes, multiple loaders can be called to construct a single page. When using `session.flash()` or `session.unset()`, you need to be sure no other loaders in the request are going to want to read that, otherwise you'll get race conditions. Typically if you're using flash, you'll want to have a single loader read it; if another loader wants a flash message, use a different key for that loader.
196+
197+
- Every time you modify session data, you must `commitSession()` or your changes will be lost. This is different than what you might be used to, where some type of middleware automatically commits session data for you.
191198

192199
## `createSession`
193200

@@ -265,9 +272,11 @@ The `expires` argument to `createData` and `updateData` is the same `Date` at wh
265272

266273
For purely cookie-based sessions (where the session data itself is stored in the session cookie with the browser, see [cookies][cookies]) you can use `createCookieSessionStorage()`.
267274

268-
The main advantage of cookie session storage is that you don't need any additional backend services or databases to use it. It can also be beneficial in some load-balanced scenarios. However, cookie-based sessions may not exceed the browser's max-allowed cookie length (typically 4kb).
275+
The main advantage of cookie session storage is that you don't need any additional backend services or databases to use it. It can also be beneficial in some load-balanced scenarios.
269276

270-
The downside is that you have to `commitSession` in almost every loader and action. If your loader or action changes the session at all, it must be committed. That means if you `session.flash` in an action, and then `session.get` in another, you must commit it for that flashed message to go away. With other session storage strategies you only have to commit it when it's created (the browser cookie doesn't need to change because it doesn't store the session data, just the key to find it elsewhere).
277+
However, cookie-based sessions may not exceed the browser's cookie size limit, typically 1024 bytes per cookie value (and 4 KB total for all cookies).
278+
279+
The other downside is that you need to update the `Set-Cookie` header in every loader and action that modifies the session. With other strategies you only need to set the session cookie once, because it doesn't actually store any session data, just the key to find it elsewhere.
271280

272281
```ts
273282
import { createCookieSessionStorage } from "@remix-run/node"; // or cloudflare/deno
@@ -289,7 +298,7 @@ This storage keeps all the cookie information in your server's memory.
289298

290299
<docs-error>This should only be used in development. Use one of the other methods in production.</docs-error>
291300

292-
```ts filename=app/sessions.ts
301+
```ts filename=app/sessions.server.ts
293302
import {
294303
createCookie,
295304
createMemorySessionStorage,
@@ -309,15 +318,15 @@ const { getSession, commitSession, destroySession } =
309318
export { getSession, commitSession, destroySession };
310319
```
311320

312-
## `createFileSessionStorage` (node)
321+
## `createFileSessionStorage` (Node.js, Deno)
313322

314323
For file-backed sessions, use `createFileSessionStorage()`. File session storage requires a file system, but this should be readily available on most cloud providers that run express, maybe with some extra configuration.
315324

316-
The advantage of file-backed sessions is that only the session ID is stored in the cookie while the rest of the data is stored in a regular file on disk, ideal for sessions with more than 4kb of data.
325+
The advantage of file-backed sessions is that only the session ID is stored in the cookie while the rest of the data is stored in a regular file on disk, ideal for sessions with more than 1024 bytes of data.
317326

318327
<docs-info>If you are deploying to a serverless function, ensure you have access to a persistent file system. They usually don't have one without extra configuration.</docs-info>
319328

320-
```ts filename=app/sessions.ts
329+
```ts filename=app/sessions.server.ts
321330
import {
322331
createCookie,
323332
createFileSessionStorage,
@@ -340,11 +349,11 @@ const { getSession, commitSession, destroySession } =
340349
export { getSession, commitSession, destroySession };
341350
```
342351

343-
## `createWorkersKVSessionStorage` (Cloudflare Workers)
352+
## `createWorkersKVSessionStorage` (Cloudflare)
344353

345354
For [Cloudflare Workers KV][cloudflare-kv] backed sessions, use `createWorkersKVSessionStorage()`.
346355

347-
The advantage of KV backed sessions is that only the session ID is stored in the cookie while the rest of the data is stored in a globally-replicated, low-latency data store with exceptionally high-read volumes with low-latency.
356+
The advantage of KV-backed sessions is that only the session ID is stored in the cookie while the rest of the data is stored in a globally replicated, low-latency data store supporting exceptionally high read volumes.
348357

349358
```ts filename=app/sessions.server.ts
350359
import {
@@ -368,11 +377,11 @@ const { getSession, commitSession, destroySession } =
368377
export { getSession, commitSession, destroySession };
369378
```
370379

371-
## `createArcTableSessionStorage` (architect, Amazon DynamoDB)
380+
## `createArcTableSessionStorage` (Architect, Amazon DynamoDB)
372381

373382
For [Amazon DynamoDB][amazon-dynamo-db] backed sessions, use `createArcTableSessionStorage()`.
374383

375-
The advantage of DynamoDB backed sessions is that only the session ID is stored in the cookie while the rest of the data is stored in a globally replicated, low-latency data store with exceptionally high read volumes with low-latency.
384+
The advantage of DynamoDB-backed sessions is that only the session ID is stored in the cookie while the rest of the data is stored in a globally replicated, low-latency data store supporting exceptionally high read volumes.
376385

377386
```
378387
# app.arc
@@ -410,19 +419,26 @@ export { getSession, commitSession, destroySession };
410419

411420
## Session API
412421

413-
After retrieving a session with `getSession`, the returned session object has a handful of methods and properties:
422+
After retrieving a session with `getSession()`, the returned session object has a handful of methods to read and update the retrieved session data:
414423

415424
```tsx
416425
export async function action({ request }: ActionArgs) {
417426
const session = await getSession(
418427
request.headers.get("Cookie")
419428
);
429+
420430
session.get("foo");
421-
session.has("bar");
431+
session.unset("bar");
422432
// etc.
433+
434+
await commitSession(session);
423435
}
424436
```
425437

438+
<docs-warning>Every time you modify session data, you must `commitSession()` or your changes will be lost.</docs-warning>
439+
440+
<docs-warning>When using cookie session storage, you must `Set-Cookie` every time you `commitSession()` or your changes will be lost.</docs-warning>
441+
426442
### `session.has(key)`
427443

428444
Returns `true` if the session has a variable with the given `name`.
@@ -441,118 +457,28 @@ session.set("userId", "1234");
441457

442458
### `session.flash(key, value)`
443459

444-
Sets a session value that will be unset the first time it is read. After that, it's gone. Most useful for "flash messages" and server-side form validation messages:
445-
446-
```tsx
447-
import { commitSession, getSession } from "../sessions";
448-
449-
export async function action({
450-
params,
451-
request,
452-
}: ActionArgs) {
453-
const session = await getSession(
454-
request.headers.get("Cookie")
455-
);
456-
const deletedProject = await archiveProject(
457-
params.projectId
458-
);
459-
460-
session.flash(
461-
"globalMessage",
462-
`Project ${deletedProject.name} successfully archived`
463-
);
464-
465-
return redirect("/dashboard", {
466-
headers: {
467-
"Set-Cookie": await commitSession(session),
468-
},
469-
});
470-
}
471-
```
472-
473-
Now we can read the message in a loader.
474-
475-
<docs-info>You must commit the session whenever you read a `flash`. This is different than what you might be used to, where some type of middleware automatically sets the cookie header for you.</docs-info>
476-
477-
```tsx
478-
import { json } from "@remix-run/node"; // or cloudflare/deno
479-
import {
480-
Meta,
481-
Links,
482-
Scripts,
483-
Outlet,
484-
} from "@remix-run/react";
485-
486-
import { getSession, commitSession } from "./sessions";
460+
Sets a session value that will be unset the first time it is read in a subsequent request. After that, it's gone. Most useful for "flash messages" and server-side form validation messages:
487461

488-
export async function loader({ request }: LoaderArgs) {
489-
const session = await getSession(
490-
request.headers.get("Cookie")
491-
);
492-
const message = session.get("globalMessage") || null;
493-
494-
return json(
495-
{ message },
496-
{
497-
headers: {
498-
// only necessary with cookieSessionStorage
499-
"Set-Cookie": await commitSession(session),
500-
},
501-
}
502-
);
503-
}
504-
505-
export default function App() {
506-
const { message } = useLoaderData<typeof loader>();
507-
508-
return (
509-
<html>
510-
<head>
511-
<Meta />
512-
<Links />
513-
</head>
514-
<body>
515-
{message ? (
516-
<div className="flash">{message}</div>
517-
) : null}
518-
<Outlet />
519-
<Scripts />
520-
</body>
521-
</html>
522-
);
523-
}
462+
```ts
463+
session.flash("globalMessage", "Project successfully archived");
524464
```
525465

526-
### `session.get()`
466+
### `session.get(key)`
527467

528468
Accesses a session value from a previous request:
529469

530470
```ts
531471
session.get("name");
532472
```
533473

534-
### `session.unset()`
474+
### `session.unset(key)`
535475

536476
Removes a value from the session.
537477

538478
```ts
539479
session.unset("name");
540480
```
541481

542-
<docs-info>When using cookieSessionStorage, you must commit the session whenever you `unset`</docs-info>
543-
544-
```tsx
545-
export async function loader({ request }: LoaderArgs) {
546-
// ...
547-
548-
return json(data, {
549-
headers: {
550-
"Set-Cookie": await commitSession(session),
551-
},
552-
});
553-
}
554-
```
555-
556482
[cookies]: ./cookies
557483
[constraints]: ../guides/constraints
558484
[csrf]: https://developer.mozilla.org/en-US/docs/Glossary/CSRF

0 commit comments

Comments
 (0)