Next.js
BlaC works in Next.js without extra packages. The main considerations are (1) registering server-side state isolation the same way as any SSR app, and (2) understanding which rendering boundary owns your blocs.
App Router
Section titled “App Router”Mark bloc-consuming components 'use client'
Section titled “Mark bloc-consuming components 'use client'”Blocs live in JavaScript module state and require browser APIs (event listeners, subscriptions). Any component that calls useBloc must be a Client Component — add 'use client' at the top of the file, same as any other stateful component (see React getting started for a full useBloc example):
'use client';
import { useBloc } from '@blac/react';import { CounterCubit } from './CounterCubit';
export function Counter() { const [state, cubit] = useBloc(CounterCubit); return <button onClick={cubit.increment}>Count: {state.count}</button>;}Do not read or write blocs in Server Components
Section titled “Do not read or write blocs in Server Components”Server Components run on the server, in the same Node.js process that handles every request. The module-level registry is shared across all concurrent requests in that process. Reading or mutating a bloc from a Server Component is a data-leakage hazard — see SSR & per-request isolation for the full explanation.
Feeding server data to blocs via args
Section titled “Feeding server data to blocs via args”Fetch data in the Server Component and pass it as props. Receive the props in a Client Component and forward them to useBloc as args so the bloc’s init method can set the initial state before the first render:
// app/users/[id]/page.tsx — Server Component (no 'use client')import { UserCard } from './UserCard';
export default async function UserPage({ params }: { params: { id: string } }) { // Fetch happens on the server; no blocs involved here. const userData = await fetch(`/api/users/${params.id}`).then((r) => r.json()); return <UserCard initialData={userData} userId={params.id} />;}// app/users/[id]/UserCard.tsx — Client Component'use client';
import { useBloc } from '@blac/react';// UserCardCubit's init(args) sets state from args.initialData — see /guide/inputsimport { UserCardCubit, type UserData } from './UserCardCubit';
export function UserCard({ userId, initialData,}: { userId: string; initialData: UserData;}) { const [state] = useBloc(UserCardCubit, { args: { userId, initialData } }); return <div>{state.name}</div>;}The args value keys the instance (different userId → different instance) and is passed to init once at creation — the initial state is correct on the first render, no flash. See Passing Inputs for the init(args) mechanics.
Store-per-request for route handlers and Server Actions
Section titled “Store-per-request for route handlers and Server Actions”If you need to invoke bloc logic inside a route handler, a Server Action, or generateStaticParams, wrap it in the withRequestRegistry helper from SSR & per-request isolation so each request gets its own isolated registry:
import { NextRequest, NextResponse } from 'next/server';import { withRequestRegistry } from '@/lib/registry'; // the ssr.md helperimport { acquire, release } from '@blac/core';import { SummaryCubit } from '@/blocs/SummaryCubit';
export async function GET(req: NextRequest) { const result = await withRequestRegistry(async () => { const bloc = acquire(SummaryCubit); await bloc.generate(req.nextUrl.searchParams.get('q') ?? ''); const summary = bloc.state.text; release(SummaryCubit); return summary; }); return NextResponse.json({ summary: result });}Pages Router
Section titled “Pages Router”The Pages Router uses getServerSideProps (or getStaticProps) to fetch data, with no 'use client' directive — every page component is bundled for the browser regardless. The rules are otherwise identical to the App Router: never touch the registry inside getServerSideProps, and pass its return value down as page props to feed a bloc’s args, exactly like the args example above.
export const getServerSideProps: GetServerSideProps<PageProps> = async (ctx) => { const userId = ctx.params?.id as string; const initialData = await fetch(`https://api.example.com/users/${userId}`).then((r) => r.json()); return { props: { userId, initialData } }; // no blocs touched here};
export default function UserPage({ userId, initialData }: PageProps) { return <UserCard userId={userId} initialData={initialData} />;}Plugin setup
Section titled “Plugin setup”Install plugins in a module imported once by the client bundle — app/providers.tsx (App Router) or pages/_app.tsx (Pages Router), marked 'use client' so IndexedDB/localStorage-based plugins never run in the server bundle:
'use client';
import { getPluginManager } from '@blac/core';import { createIndexedDbPersistPlugin } from '@blac/plugin-persist';import { UserSettingsCubit } from '@/blocs/UserSettingsCubit';
const persist = createIndexedDbPersistPlugin();persist.persist(UserSettingsCubit);getPluginManager().install(persist);
export function Providers({ children }: { children: React.ReactNode }) { return <>{children}</>;}See also
Section titled “See also”- SSR & per-request isolation — the
withRequestRegistrypattern and the async-concurrency caveats - Passing Inputs —
args,init, and instance identity - Instance Management —
acquire,release, and the ref-counting lifecycle - Persistence Plugin — IndexedDB persistence setup