DocsNext.js (App Router)4 min read

Next.js (App Router)

The SDK works on both sides of the boundary, but they want different things: a client component repaints, a server component cannot. Pick per string, not per app.

lib/siyak.ts
import { init } from 'siyak-js';

// One module, two instances: the client one keeps localStorage, the server one
// must not touch it.
export const siyak = init({
  apiKey: process.env.NEXT_PUBLIC_SIYAK_KEY!,
  endpoint: process.env.NEXT_PUBLIC_SIYAK_URL!,
  target: 'ar',
  cache: typeof window === 'undefined' ? false : undefined,
});

Client components

app/(shop)/checkout/page.tsx
'use client';
import { siyak } from '@/lib/siyak';

export default function Checkout() {
  return <button>{siyak.t('Continue', 'screen:checkout')}</button>;
}

Same as React: the first paint may be in your source language, the batch lands a moment later. Add the effect that awaits siyak.onReady once at the layout and re-renders, and every page below it behaves.

Server components and route handlers

app/[locale]/page.tsx
import { siyak } from '@/lib/siyak';

export default async function Page() {
  siyak.t('Continue', 'screen:checkout');
  await siyak.flush();                        // no repaint up here
  return <p>{siyak.t('Continue', 'screen:checkout')}</p>;
}
  • flush() before you emit markup, or the HTML ships the source text — correct but not translated.
  • Every render that flushes puts a round trip into your time to first byte. Cache the result (unstable_cache, or a full route cache): an approved string is answered from cache, so that hop costs you no model call.
  • NEXT_PUBLIC_ means the key is in the client bundle. For a browser key that is the normal case; keep a separate server key if you want separate quotas.
  • Middleware and edge runtime: siyak-js needs fetch (available) and no localStorage — pass cache: false.