Server Side Rendering
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. We also make the config a function, so that we can call it once per request which allows for request-based isolation of the account state.
import { const createConfig: (props: CreateConfigProps, ui?: AlchemyAccountsUIConfig) => AlchemyAccountsConfigWithUIWraps the createConfig
that is exported from @aa-sdk/core
to allow passing an additional argument, the configuration object for the Auth Components UI (the modal and AuthCard).
createConfig } from "@account-kit/react";
import { const sepolia: Chainsepolia, function alchemy(config: AlchemyTransportConfig): AlchemyTransportCreates an Alchemy transport with the specified configuration options. When sending all traffic to Alchemy, you must pass in one of rpcUrl, apiKey, or jwt. If you want to send Bundler and Paymaster traffic to Alchemy and Node traffic to a different RPC, you must pass in alchemyConnection and nodeRpcUrl.
alchemy } from "@account-kit/infra";
export const const config: () => AlchemyAccountsConfigWithUIconfig = () =>
function createConfig(props: CreateConfigProps, ui?: AlchemyAccountsUIConfig): AlchemyAccountsConfigWithUIWraps the createConfig
that is exported from @aa-sdk/core
to allow passing an additional argument, the configuration object for the Auth Components UI (the modal and AuthCard).
createConfig({
// required
transport: AlchemyTransporttransport: function alchemy(config: AlchemyTransportConfig): AlchemyTransportCreates an Alchemy transport with the specified configuration options. When sending all traffic to Alchemy, you must pass in one of rpcUrl, apiKey, or jwt. If you want to send Bundler and Paymaster traffic to Alchemy and Node traffic to a different RPC, you must pass in alchemyConnection and nodeRpcUrl.
alchemy({ rpcUrl: stringrpcUrl: "/api/rpc" }),
chain: Chainchain: const sepolia: Chainsepolia,
ssr?: boolean | undefinedEnable this parameter if you are using the config in an SSR setting (eg. NextJS) Turing this setting on will disable automatic hydration of the client store
ssr: true,
});
This setting will defer hydration of the account state to the client after the initial mount.
Persisting the Account State
Cookie Storage
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.
import {
const createConfig: (props: CreateConfigProps, ui?: AlchemyAccountsUIConfig) => AlchemyAccountsConfigWithUIWraps the createConfig
that is exported from @aa-sdk/core
to allow passing an additional argument, the configuration object for the Auth Components UI (the modal and AuthCard).
createConfig,
const cookieStorage: (config?: {
sessionLength?: number;
domain?: string;
}) => StorageFunction to create cookie based Storage
cookieStorage,
} from "@account-kit/react";
import { const sepolia: Chainsepolia, function alchemy(config: AlchemyTransportConfig): AlchemyTransportCreates an Alchemy transport with the specified configuration options. When sending all traffic to Alchemy, you must pass in one of rpcUrl, apiKey, or jwt. If you want to send Bundler and Paymaster traffic to Alchemy and Node traffic to a different RPC, you must pass in alchemyConnection and nodeRpcUrl.
alchemy } from "@account-kit/infra";
import { class QueryClientQueryClient } from "@tanstack/react-query";
export const const queryClient: QueryClientqueryClient = new new QueryClient(config?: QueryClientConfig): QueryClientQueryClient();
export const const config: () => AlchemyAccountsConfigWithUIconfig = () =>
function createConfig(props: CreateConfigProps, ui?: AlchemyAccountsUIConfig): AlchemyAccountsConfigWithUIWraps the createConfig
that is exported from @aa-sdk/core
to allow passing an additional argument, the configuration object for the Auth Components UI (the modal and AuthCard).
createConfig({
// required
transport: AlchemyTransporttransport: function alchemy(config: AlchemyTransportConfig): AlchemyTransportCreates an Alchemy transport with the specified configuration options. When sending all traffic to Alchemy, you must pass in one of rpcUrl, apiKey, or jwt. If you want to send Bundler and Paymaster traffic to Alchemy and Node traffic to a different RPC, you must pass in alchemyConnection and nodeRpcUrl.
alchemy({ rpcUrl: stringrpcUrl: "/api/rpc" }),
chain: Chainchain: const sepolia: Chainsepolia,
ssr?: boolean | undefinedEnable this parameter if you are using the config in an SSR setting (eg. NextJS) Turing this setting on will disable automatic hydration of the client store
ssr: true,
storage?: CreateStorageFn | undefinedstorage: const cookieStorage: (config?: {
sessionLength?: number;
domain?: string;
}) => StorageFunction to create cookie based 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:
import React from "react";
import { function cookieToInitialState(config: AlchemyAccountsConfig, cookie?: string): StoredState | undefinedConverts a cookie into an initial state object
cookieToInitialState } from "@account-kit/core";
import type { import MetadataMetadata } from "next";
import { import InterInter } from "next/font/google";
import { import headersheaders } from "next/headers";
import { import configconfig } from "./config";
import "./globals.css";
import { import ProvidersProviders } from "./providers";
const const inter: anyinter = import InterInter({ subsets: string[]subsets: ["latin"] });
export const const metadata: Metadatametadata: import MetadataMetadata = {
title: stringtitle: "Embedded Accounts Getting Started",
description: stringdescription: "Embedded Accounts Quickstart Guide",
};
export default function function RootLayout({ children, }: Readonly<{
children: React.ReactNode;
}>): JSX.ElementRootLayout({
children: React.ReactNodechildren,
}: type Readonly<T> = { readonly [P in keyof T]: T[P]; }Make all properties in T readonly
Readonly<{
children: React.ReactNodechildren: React.type React.ReactNode = string | number | bigint | boolean | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | React.ReactPortal | Promise<...> | null | undefinedRepresents all of the things React can render.
Where ReactElement only represents JSX, ReactNode
represents everything that can be rendered.
ReactNode;
}>) {
// This will allow us to persist state across page boundaries
const const initialState: StoredState | undefinedinitialState = function cookieToInitialState(config: AlchemyAccountsConfig, cookie?: string): StoredState | undefinedConverts a cookie into an initial state object
cookieToInitialState(
// the config here is just used to compute the initial state
import configconfig(),
import headersheaders().anyget("cookie") ?? var undefinedundefined,
);
return (
<React.JSX.IntrinsicElements.html: React.DetailedHTMLProps<React.HtmlHTMLAttributes<HTMLHtmlElement>, HTMLHtmlElement>html React.HTMLAttributes<HTMLHtmlElement>.lang?: string | undefinedlang="en">
<React.JSX.IntrinsicElements.body: React.DetailedHTMLProps<React.HTMLAttributes<HTMLBodyElement>, HTMLBodyElement>body React.HTMLAttributes<HTMLBodyElement>.className?: string | undefinedclassName={const inter: anyinter.anyclassName}>
<import ProvidersProviders initialState: StoredState | undefinedinitialState={const initialState: StoredState | undefinedinitialState}>{children: React.ReactNodechildren}</import ProvidersProviders>
</React.JSX.IntrinsicElements.body: React.DetailedHTMLProps<React.HTMLAttributes<HTMLBodyElement>, HTMLBodyElement>body>
</React.JSX.IntrinsicElements.html: React.DetailedHTMLProps<React.HtmlHTMLAttributes<HTMLHtmlElement>, HTMLHtmlElement>html>
);
}
"use client";
import React, { function useRef<T>(initialValue: T): React.RefObject<T> (+2 overloads)useRef
returns a mutable ref object whose .current
property is initialized to the passed argument (initialValue
). The returned object will persist for the full lifetime of the component.
Note that useRef()
is useful for more than the ref
attribute. It’s handy for keeping any mutable value around similar to how you’d use instance fields in classes.
useRef } from "react";
import { type AlchemyClientState = {
alchemy: Omit<StoreState, "signer" | "accounts" | "bundlerClient">;
wagmi?: State;
}AlchemyClientState } from "@account-kit/core";
import {
const AlchemyAccountProvider: (props: React.PropsWithChildren<AlchemyAccountsProviderProps>) => JSX.ElementProvider for Alchemy accounts.
AlchemyAccountProvider,
type type AlchemyAccountsConfigWithUI = AlchemyAccountsConfig & {
ui?: AlchemyAccountsUIConfig;
}AlchemyAccountsConfigWithUI,
} from "@account-kit/react";
import { const QueryClientProvider: ({ client, children, }: QueryClientProviderProps) => React.JSX.ElementQueryClientProvider } from "@tanstack/react-query";
import { type PropsWithChildren<P = unknown> = P & {
children?: React.ReactNode | undefined;
}PropsWithChildren, const Suspense: React.ExoticComponent<React.SuspenseProps>Lets you display a fallback until its children have finished loading.
Suspense } from "react";
import { import configconfig, import queryClientqueryClient } from "./config";
export const const Providers: (props: PropsWithChildren<{
initialState?: AlchemyClientState;
}>) => JSX.ElementProviders = (
props: React.PropsWithChildren<{
initialState?: AlchemyClientState;
}>props: type PropsWithChildren<P = unknown> = P & {
children?: React.ReactNode | undefined;
}PropsWithChildren<{ initialState?: StoredState | undefinedinitialState?: type AlchemyClientState = {
alchemy: Omit<StoreState, "signer" | "accounts" | "bundlerClient">;
wagmi?: State;
}AlchemyClientState }>,
) => {
const const ref: React.RefObject<AlchemyAccountsConfigWithUI>ref = useRef<AlchemyAccountsConfigWithUI>(initialValue: AlchemyAccountsConfigWithUI): React.RefObject<AlchemyAccountsConfigWithUI> (+2 overloads)useRef
returns a mutable ref object whose .current
property is initialized to the passed argument (initialValue
). The returned object will persist for the full lifetime of the component.
Note that useRef()
is useful for more than the ref
attribute. It’s handy for keeping any mutable value around similar to how you’d use instance fields in classes.
useRef<type AlchemyAccountsConfigWithUI = AlchemyAccountsConfig & {
ui?: AlchemyAccountsUIConfig;
}AlchemyAccountsConfigWithUI>();
if (!const ref: React.RefObject<AlchemyAccountsConfigWithUI>ref.React.RefObject<AlchemyAccountsConfigWithUI>.current: AlchemyAccountsConfigWithUIThe current value of the ref.
current) {
const ref: React.RefObject<AlchemyAccountsConfigWithUI>ref.React.RefObject<AlchemyAccountsConfigWithUI>.current: AlchemyAccountsConfigWithUIThe current value of the ref.
current = import configconfig();
}
return (
<const Suspense: React.ExoticComponent<React.SuspenseProps>Lets you display a fallback until its children have finished loading.
Suspense>
<const QueryClientProvider: ({ client, children, }: QueryClientProviderProps) => React.JSX.ElementQueryClientProvider client: QueryClientclient={import queryClientqueryClient}>
<const AlchemyAccountProvider: (props: React.PropsWithChildren<AlchemyAccountsProviderProps>) => JSX.ElementProvider for Alchemy accounts.
AlchemyAccountProvider
config: AlchemyAccountsConfigWithUIconfig={const ref: React.RefObject<AlchemyAccountsConfigWithUI>ref.React.RefObject<AlchemyAccountsConfigWithUI>.current: AlchemyAccountsConfigWithUIThe current value of the ref.
current!}
queryClient: QueryClientqueryClient={import queryClientqueryClient}
initialState?: StoredState | undefinedinitialState={props: React.PropsWithChildren<{
initialState?: AlchemyClientState;
}>props.initialState?: StoredState | undefinedinitialState}
>
{props: React.PropsWithChildren<{
initialState?: AlchemyClientState;
}>props.children?: React.ReactNodechildren}
</const AlchemyAccountProvider: (props: React.PropsWithChildren<AlchemyAccountsProviderProps>) => JSX.ElementProvider for Alchemy accounts.
AlchemyAccountProvider>
</const QueryClientProvider: ({ client, children, }: QueryClientProviderProps) => React.JSX.ElementQueryClientProvider>
</const Suspense: React.ExoticComponent<React.SuspenseProps>Lets you display a fallback until its children have finished loading.
Suspense>
);
};
import { const createConfig: (props: CreateConfigProps, ui?: AlchemyAccountsUIConfig) => AlchemyAccountsConfigWithUIWraps the createConfig
that is exported from @aa-sdk/core
to allow passing an additional argument, the configuration object for the Auth Components UI (the modal and AuthCard).
createConfig, const cookieStorage: (config?: {
sessionLength?: number;
domain?: string;
}) => StorageFunction to create cookie based Storage
cookieStorage } from "@account-kit/react";
import { const sepolia: Chainsepolia, function alchemy(config: AlchemyTransportConfig): AlchemyTransportCreates an Alchemy transport with the specified configuration options. When sending all traffic to Alchemy, you must pass in one of rpcUrl, apiKey, or jwt. If you want to send Bundler and Paymaster traffic to Alchemy and Node traffic to a different RPC, you must pass in alchemyConnection and nodeRpcUrl.
alchemy } from "@account-kit/infra";
import { class QueryClientQueryClient } from "@tanstack/react-query";
export const const queryClient: QueryClientqueryClient = new new QueryClient(config?: QueryClientConfig): QueryClientQueryClient();
// When using SSR, you need to be able to create a config per request
// This is to avoid sharing state between requests (eg. signed in users)
export const const config: () => AlchemyAccountsConfigWithUIconfig = () =>
function createConfig(props: CreateConfigProps, ui?: AlchemyAccountsUIConfig): AlchemyAccountsConfigWithUIWraps the createConfig
that is exported from @aa-sdk/core
to allow passing an additional argument, the configuration object for the Auth Components UI (the modal and AuthCard).
createConfig({
transport: AlchemyTransporttransport: function alchemy(config: AlchemyTransportConfig): AlchemyTransportCreates an Alchemy transport with the specified configuration options. When sending all traffic to Alchemy, you must pass in one of rpcUrl, apiKey, or jwt. If you want to send Bundler and Paymaster traffic to Alchemy and Node traffic to a different RPC, you must pass in alchemyConnection and nodeRpcUrl.
alchemy({ rpcUrl: stringrpcUrl: "/api/rpc" }),
chain: Chainchain: const sepolia: Chainsepolia,
ssr?: boolean | undefinedEnable this parameter if you are using the config in an SSR setting (eg. NextJS) Turing this setting on will disable automatic hydration of the client store
ssr: true,
storage?: CreateStorageFn | undefinedstorage: const cookieStorage: (config?: {
sessionLength?: number;
domain?: string;
}) => StorageFunction to create cookie based Storage
cookieStorage,
});
Next.js Pages Directory
Coming soon!
Vanilla SSR
Coming soon!