Skip to content

Server Side Rendering (SSR)

When using the React hooks exported by Account Kit in a server-side rendered setting, you will see inconsistencies of the user state between the server and the client. This will lead to flashes of content when a user is logged in. To avoid this, the account state can be optimistically loaded on the server and passed to the client.

To enable this setting, you can set ssr: true when creating a config.

ts
import { createConfig } from "@alchemy/aa-alchemy/config";
import { sepolia } from "@alchemy/aa-core";

export const config = createConfig({
  // required
  rpcUrl: "/api/rpc",
  chain: sepolia,
  ssr: true, 
});

This setting will defer hydration of the account state to the client after the initial mount.

Persisting the Account State

To consistently pass the state between the server and the client, you can pass in a cookie storage to the config object created above. The cookie storage allows the client state to be written serialized to a cookie which can be passed along to the server on each request. This allows the server to have access to certain parts of the account state when rendering, ensuring a consistent render between client and server (eg. user's address displayed in the top nav). Instances which can only be created on the client will still not be available on the server, however. This includes the signer or smart contract account instances.

ts
import {
  createConfig,
  cookieStorage, 
} from "@alchemy/aa-alchemy/config";
import { sepolia } from "@alchemy/aa-core";

export const config = createConfig({
  // required
  rpcUrl: "/api/rpc",
  chain: sepolia,
  ssr: true, 
  storage: cookieStorage, 
});

Now, depending on your application, you can get the state from cookies and pass in the initialState to the AlchemyAccountProvider to hydrate the account state on the client.

Next.js App Directory

If you are using NextJS App Directory, you can read the cookie state and pass it to the providers like so:

tsx
import { cookieToInitialState } from "@alchemy/aa-alchemy/config";
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import { headers } from "next/headers";
import { config } from "./config";
import "./globals.css";
import { Providers } from "./providers";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "Embedded Accounts Getting Started",
  description: "Embedded Accounts Quickstart Guide",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  // This will allow us to persist state across page boundaries
  const initialState = cookieToInitialState(config, headers().get("cookie"));

  return (
    <html lang="en">
      <body className={inter.className}>
        <Providers initialState={initialState}>{children}</Providers>
      </body>
    </html>
  );
}
tsx
"use client";

import { AlchemyClientState } from "@alchemy/aa-alchemy/config";
import { AlchemyAccountProvider } from "@alchemy/aa-alchemy/react";
import { QueryClientProvider } from "@tanstack/react-query";
import { PropsWithChildren, Suspense } from "react";
import { config, queryClient } from "./config";

export const Providers = (
  props: PropsWithChildren<{ initialState?: AlchemyClientState }>
) => {
  return (
    <Suspense>
      <QueryClientProvider client={queryClient}>
        <AlchemyAccountProvider
          config={config}
          queryClient={queryClient}
          initialState={props.initialState}
        >
          {props.children}
        </AlchemyAccountProvider>
      </QueryClientProvider>
    </Suspense>
  );
};

Next.js Pages Directory

Coming soon!

Vanilla SSR

Coming soon!