🚧 Experimental
Introduction
This package provides shared state between server and client components in Next.js App router. It utilizes server-side session storage to store the state, seamlessly synchronizing it across components. Additionally, it offers a easy approach for reading and writing state data from both server and client sides.
Setup
Install package:
npm i next-server-state
Setup dynamic route: create file with path: /pages/api/server-state/[...path].ts
// /pages/api/server-state/[...path].tsimport { setupServerStateRoutes } from 'next-server-state';export default setupServerStateRoutes();
Setup your server state:
// /lib/my-server-state.tsimport {createServerState,getServerState,useServerState} from 'next-server-state';type MyServerStateProps = {first_name: string;last_name: string;counter: number;};export const MyServerState =createServerState<MyServerStateProps>('my-state-unique-key', {first_name: '',last_name: '',counter: ''});export function useMyServerState() {return useServerState<MyServerStateProps>(MyServerState);}export async function getMyServerState() {return getServerState<MyServerStateProps>(ExampleOneServerState);}
Setup state context: Wrap your server and client component of which you wish to consume the server state in
import { MyServerState } from '../lib/my-server-state.ts'; // import state variable from the file you initialised it inexport const MyParentComponent = () => {return (<MyServerState.Provider>// ...your server and client components go here</MyServerState.Provider>)}
Usage
How you use the server state depends on whether you're using it on server side or client side:
- Client Componentsconst [state, updateState] = useServerSide(myServerState)
- Server Components + Server Actions + Route Handlersconst [state, updateState] = getServerSide(myServerState)
Usage cases
Client Component you can read and set state data like so:
'use client'import { useMyServerState } from '../lib/my-server-state.ts'; // import from the file you initialised the server state inexport function MyClientComponent(){const [state, updateState] = useMyServerState();const handleIncrement = ()=>{updateState({counter: Number(state.counter) + 1})}return (<div><p>Counter: <span>{state.counter}</span></p><button type="button" onClick={handleIncrement}>Increment</button></div>)}
Server Component you can read state and render it like so:
import { getMyServerState } from '../lib/my-server-state.ts'; // import from the file you initialised the server state inexport async function MyServerComponent(){const [state, updateState] = await getMyServerState();// If needed although not recommended, You can also update state on server component render// await updateState({ first_name: 'new name' })// const [newState] = await getServerState();return (<span><span>{state.first_name}</span>// ...</span>)}
Client Component with Server Actions you can read and set state data like so:
// server-actions.ts'use server'import { getMyServerState } from '../lib/my-server-state.ts'; // import from the file you initialised the server state inexport async function actionIncrementCounter(formData: FormData) {const [state, updateState] = await getMyServerState();updateState({counter: Number(state.counter ?? 0) + 1});return { message: 'Updated' };}
// MyClientComponent.ts'use client'import { useMyServerState } from '../lib/my-server-state.ts'; // import from the file you initialised the server state inimport { actionIncrementCounter } from './server-actions.ts'; // Your server actionsexport function MyClientComponent(){const [state] = useMyServerState();return (<div><p>Counter: <span>{state.counter}</span></p><form action={actionIncrementCounter}><button type="submit">Increment</button></form></div>)}
App Route Handler you can read and set state data like so:
import { getMyServerState } from '../lib/my-server-state.ts'; // import from the file you initialised the server state inexport async function POST() {const [state, updateState] = await getMyServerState();updateState({counter: Number(state.counter) + 1})return { message: 'Counter incremented' };}