Skip to content
Back to skills

Ssr

ASecurity

How to use `AtomStoreProvider`, `useAtom`, `useAtomStore`, and the hydration utilities (`HydrateAtomsScript`, `readHydration`, `serializeStore`, `serializeSnapshot`) for SSR-safe, per-request atom isolation and client hydration.

  • 9 stars
  • 0 votes
  • 0 copies
  • 7 views
  • Added May 28, 2026
developmentgoreactnextjsnode

Works with

  • cli

Security analysis

A100/100

Scanned September 30, 2026

npx -y skills add hassanzohdy/mongez-react-atom --skill ssr --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Ssr?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Ssr
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/hassanzohdy-ssr/badge)](https://www.skillsdirectory.com/skills/hassanzohdy-ssr)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: mongez-react-atom-ssr
description: |
  How to use `AtomStoreProvider`, `useAtom`, `useAtomStore`, and the hydration utilities (`HydrateAtomsScript`, `readHydration`, `serializeStore`, `serializeSnapshot`) for SSR-safe, per-request atom isolation and client hydration.
---
# SSR & stores

The module-level atom registry is shared per Node process. To keep concurrent SSR requests from leaking state into each other, wrap your tree in `<AtomStoreProvider>` — each request gets its own scoped clones of every atom that's read inside it.

## `<AtomStoreProvider>`

```tsx
import { AtomStoreProvider } from "@mongez/react-atom";

<AtomStoreProvider>
  <App />
</AtomStoreProvider>
```

Props:

```ts
type AtomStoreProviderProps = {
  store?: AtomStore;                          // bring your own; otherwise auto-created
  initialAtoms?: Atom<any>[];                 // pre-register these
  initialValues?: Record<string, unknown>;    // hydration values keyed by atom key
  children: React.ReactNode;
};
```

- An auto-created store is destroyed a tick after unmount, and not at all if the provider mounts again first, so React StrictMode's simulated remount keeps the hydrated store.
- On a server (no `document`), any atom hook with no provider above it throws, naming the atom key. Wrap the page or layout in `<AtomStoreProvider>`.
- New clones seed from the atom's `default` (or `initialValues`), never from the template's current value.
- A `store` passed via props is caller-owned (NOT destroyed by the provider).
- `initialValues` are applied via `silentUpdate` so the first render of subscribers sees the hydrated values; no flash.

## `useAtom(template | key)`

Two overloads.

```ts
useAtom(template: Atom<V, A>): Atom<V, A>            // → store-scoped clone (the template in a browser with no provider; throws on a server with no provider)
useAtom(key: string): Atom<V> | undefined             // → scoped clone by key, or undefined
```

The per-atom hooks (`useValue`, `useState`, `use`, `useWatch`) already resolve internally — you only need `useAtom(template)` explicitly when you want to call an action method from an event handler in an SSR-safe way:

```tsx
function LogoutButton() {
  const scopedAuth = useAtom(authAtom);
  return <button onClick={() => scopedAuth.logout()}>Log out</button>;
}
```

Resolution is idempotent: `useAtom(template).useValue()` and `template.useValue()` use the same clone, and an atom that is already a scoped clone comes back as this store's clone. The string overload is a legacy escape hatch — prefer the template overload.

## Hydration correctness

- `getServerSnapshot` returns the store's hydrated value, frozen at the first read, so the first client render matches the server HTML even if effects change the store during hydration.
- `persist: true` on `atom()` is read after mount (`persistHydration: "deferred"`), never at creation and never on the server. Scoped clones are not persisted.
- `use("key")` and `watch` fire for atoms declared with `default: null` once they hold an object.

## Per-instance state

`useLocalAtom({ key, default })` (or `atom({ ..., register: false })`) makes an atom that stays out of the global registry, ignores the provider and needs none on the server. `useLocalAtom` suffixes the key with `.local.<n>` so instances never share events and destroys the atom after unmount.

## `useAtomStore()`

Returns the active `AtomStore` or `null` if no provider is mounted. Use for advanced cases: snapshotting on demand, manually hydrating, integrating with a non-React layer.

## Snapshot / Hydrate

```ts
serializeSnapshot(snapshot: Record<string, unknown>, options?: { replacer, space }): string
serializeStore(store: AtomStore, options?): string
readHydration(id?: string): Record<string, unknown> | null

<HydrateAtomsScript
  snapshot={snapshotOrString}
  id="__mongez_atom_state"   // optional; default constant
  nonce={cspNonce}           // optional
/>
```

- `serializeSnapshot` / `serializeStore` produce a string safe to embed in `<script type="application/json">`: `</script>`, `<!--` and U+2028/U+2029 are escaped. (`<!--` matters because it flips the HTML parser into script-data-escaped state, which would make the real closing `</script>` be swallowed as data.)
- `<HydrateAtomsScript>` renders an inline JSON script tag. Place it once per `<AtomStoreProvider>` you want to hydrate.
- `readHydration()` is the client-side companion: parses the JSON out of the script tag. Returns `null` if missing or malformed.

### Why hydrate at all?

Without hydration, atoms on the client start from their `default` value. For data the server already has (current user, feature flags, initial page data), this means the first render shows a loading state — or worse, a different UI than the server rendered — causing a **hydration mismatch** or a visible content flash.

Hydration lets you:
1. Fetch data once on the server (no extra client round-trip on load).
2. Inline the result as JSON in the HTML.
3. Have the client's atom start pre-filled — first render matches server HTML, no flash.

### Real-world example — pre-loading the current user (Next.js App Router)

```ts
// atoms/user.ts — shared between server and client
import { atom } from "@mongez/react-atom";

type User = { id: number; name: string; role: "admin" | "viewer" } | null;

export const currentUserAtom = atom<User>({ key: "currentUser", default: null });
```

```tsx
// app/layout.tsx — SERVER component
import { createAtomStore } from "@mongez/atom";
import { AtomStoreProvider, HydrateAtomsScript } from "@mongez/react-atom";
import { getCurrentUser } from "@/lib/auth";   // your server-side auth helper
import { currentUserAtom } from "@/atoms/user";

export default async function RootLayout({ children }: { children: React.ReactNode }) {
  // Runs on the server — no client round-trip needed
  const user = await getCurrentUser();

  const store = createAtomStore();
  store.use(currentUserAtom).update(user);     // pre-fill the store

  return (
    <html lang="en">
      <body>
        <AtomStoreProvider store={store}>
          {children}
          {/* Serialises atom state into an inline <script type="application/json"> tag */}
          <HydrateAtomsScript snapshot={store.snapshot()} />
        </AtomStoreProvider>
      </body>
    </html>
  );
}
```

```tsx
// app/providers.tsx — CLIENT boundary
"use client";
import { AtomStoreProvider, readHydration } from "@mongez/react-atom";

export function ClientProviders({ children }: { children: React.ReactNode }) {
  // readHydration() parses the JSON script tag injected by the server.
  // Pass it as initialValues so the client store starts pre-filled.
  return (
    <AtomStoreProvider initialValues={readHydration() ?? undefined}>
      {children}
    </AtomStoreProvider>
  );
}
```

```tsx
// components/UserMenu.tsx — any client component
"use client";
import { currentUserAtom } from "@/atoms/user";

export function UserMenu() {
  const user = currentUserAtom.useValue();

  // user is never null on first render — the server already fetched it.
  // No loading spinner, no hydration mismatch.
  if (!user) return <a href="/login">Sign in</a>;
  return (
    <div>
      <span>{user.name}</span>
      {user.role === "admin" && <a href="/admin">Admin</a>}
    </div>
  );
}
```

Without this pattern `currentUserAtom.useValue()` would be `null` on the client's first render even though the server knew the user — you'd see a brief "Sign in" flicker before the client fetched and re-rendered.

## End-to-end minimal example

```tsx
// server component
import { createAtomStore } from "@mongez/atom";
import { AtomStoreProvider, HydrateAtomsScript } from "@mongez/react-atom";

export default function Layout({ children }) {
  const store = createAtomStore();
  store.use(someAtom).update(serverFetchedValue);
  return (
    <AtomStoreProvider store={store}>
      {children}
      <HydrateAtomsScript snapshot={store.snapshot()} />
    </AtomStoreProvider>
  );
}
```

```tsx
// client root
"use client";
import { AtomStoreProvider, readHydration } from "@mongez/react-atom";

export function ClientRoot({ children }) {
  return (
    <AtomStoreProvider initialValues={readHydration() ?? undefined}>
      {children}
    </AtomStoreProvider>
  );
}
```

## Concurrent isolation

`<AtomStoreProvider>` instances are independent. Two providers in different parts of the tree don't share state — useful for multi-tenant dashboards or A/B split views.

```tsx
<>
  <AtomStoreProvider>
    <TenantA />
  </AtomStoreProvider>
  <AtomStoreProvider>
    <TenantB />
  </AtomStoreProvider>
</>
```

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.