Next.js
Use the SDK in Next.js Server Components with per-call caching and server-owned sessions.
A
JFetch public Cavuno data in App Router Server Components. Keep the SDK client in a server-only module, pass Next.js cache controls through the method’s trailing options, and keep board-user sessions in an application-owned httpOnly cookie.
Prerequisites
- A Next.js App Router project running on Node.js 20 or later.
@cavuno/boardinstalled.- Your
pk_…publishable key from Settings → Developer → SDK in the Cavuno dashboard. The SDK uses Cavuno’s production API automatically.
1. Add the environment values
Create or update .env.local:
1NEXT_PUBLIC_CAVUNO_BOARD=pk_your_32_character_hex_key
Both values are public-safe. Next.js inlines NEXT_PUBLIC_… values into browser bundles at build time, so changing them requires a new build. The examples keep SDK calls on the server even though the configuration itself is public.
2. Create one stateless server client
Create lib/cavuno.ts:
123456789101112131415161718192021222324import 'server-only';import {createBoardClient,type FetchOptions,} from '@cavuno/board';const boardKey = process.env.NEXT_PUBLIC_CAVUNO_BOARD;if (!boardKey) {throw new Error('Set NEXT_PUBLIC_CAVUNO_BOARD');}export const board = createBoardClient({board: boardKey,auth: { storage: 'nostore' },});export type NextFetchOptions = FetchOptions & {next?: {revalidate?: false | 0 | number;tags?: string[];};};
FetchOptions deliberately accepts standard RequestInit. The intersection documents the server-side next extension while preserving the SDK’s option type.
3. Render a jobs page
Create app/jobs/page.tsx:
123456789101112131415161718192021222324252627282930313233import Link from 'next/link';import { board, type NextFetchOptions } from '@/lib/cavuno';const fetchOptions: NextFetchOptions = {next: { revalidate: 60, tags: ['cavuno:jobs'] },};export default async function JobsPage() {const { data: jobs } = await board.jobs.list({ limit: 20, sort: 'newest' },fetchOptions,);return (<main><h1>Open roles</h1><ul>{jobs.map((job) => (<li key={job.id}>{job.company ? (<Link href={`/companies/${job.company.slug}/jobs/${job.slug}`}>{job.title} at {job.company.name}</Link>) : (job.title)}</li>))}</ul></main>);}
Next.js extends server-side fetch with next.revalidate and next.tags. The SDK passes these fields through unchanged. Do not combine a positive revalidate value with cache: 'no-store'; Next.js treats those controls as conflicting.
4. Render a canonical job detail
Create app/companies/[companySlug]/jobs/[jobSlug]/page.tsx:
12345678910111213141516171819202122232425import { isNotFound } from '@cavuno/board';import { notFound } from 'next/navigation';import { board } from '@/lib/cavuno';export default async function JobPage({params,}: {params: Promise<{ companySlug: string; jobSlug: string }>;}) {const { jobSlug } = await params;try {const job = await board.jobs.retrieve(jobSlug);return (<main><h1>{job.title}</h1><p>{job.company.name}</p></main>);} catch (error) {if (isNotFound(error)) notFound();throw error;}}
The API retrieves the job by job slug. The company segment preserves the canonical public URL and should be checked or redirected by your route policy if it does not match job.company.slug.
5. Keep authenticated reads private
Read the application’s cookie in server code, parse it with @cavuno/board/server, and pass the access token per call. Create lib/current-board-user.ts:
123456789101112131415161718import 'server-only';import { parseSessionCookie } from '@cavuno/board/server';import { headers } from 'next/headers';import { board } from '@/lib/cavuno';export async function currentBoardUser() {const cookieHeader = (await headers()).get('cookie');const session = parseSessionCookie(cookieHeader);if (!session) return null;return board.me.retrieve(undefined, {cache: 'no-store',headers: { authorization: `Bearer ${session.accessToken}` },});}
Do not pass the session or bearer token to a Client Component. Write or clear the cookie in a Server Function or Route Handler; Next.js cannot set response cookies while rendering a Server Component.
Never apply public tags or shared caching to a request that reads cookies or authorization. A cache key based only on the URL can replay one user’s data to another.
Verify the integration
- Run the Next.js typecheck and production build.
- Open
/jobsand compare the board name/jobs with Cavuno. - Open a valid canonical job route, then an invalid job slug and confirm the Next.js not-found UI renders.
- Inspect the rendered HTML and React payload for an authenticated page; no bearer or refresh token should appear.
- Sign in as two test users in separate sessions and confirm
currentBoardUser()returns the correct identity for each.
If public data looks stale during development, perform a full navigation or reload before changing cache code—Next.js can reuse Server Component fetch responses across hot reloads.
Continue with SSR sessions before implementing login, refresh, and logout routes.
Pages Router legacy appendix
The Pages Router remains supported by Next.js, but App Router is the primary guide because it is the framework’s current React model. In a legacy Pages project, create the same stateless client in a server-only module, load public data in getStaticProps or getServerSideProps, read sessions only in server code, and return { notFound: true } for isNotFound(error). Do not call private SDK methods from getInitialProps or ship bearer tokens in page props.
123456789export async function getServerSideProps({ params }) {try {const job = await board.jobs.retrieve(params.jobSlug);return { props: { job } };} catch (error) {if (isNotFound(error)) return { notFound: true };throw error;}}