@comvi/next API Reference
@comvi/next extends Comvi i18n with Next.js-specific utilities: Server Component support, locale routing middleware, navigation helpers, and static generation tools. Runtime-specific APIs are split across entry points: import shared setup from @comvi/next, server helpers from @comvi/next/server, Client Component hooks/components from @comvi/next/client, middleware from @comvi/next/middleware, routing helpers from @comvi/next/routing, and navigation components from @comvi/next/navigation.
createNextI18n(options)
Section titled “createNextI18n(options)”Creates an i18n instance with Next.js-specific configuration. Extends the standard createI18n options with server-side and routing settings.
import { createNextI18n } from '@comvi/next';import { FetchLoader } from '@comvi/plugin-fetch-loader';
export const { i18n, routing } = createNextI18n({ locales: ['en', 'de', 'fr'], defaultLocale: 'en', fallbackLocale: 'en',}) .use(FetchLoader({ cdnUrl: 'https://cdn.comvi.io/your-distribution-id', }));Options
Section titled “Options”interface CreateNextI18nOptions { // routing (required) locales: string[]; defaultLocale: string; localePrefix?: "always" | "as-needed" | "never"; // default "as-needed" pathnames?: RoutingConfig["pathnames"]; // locale-specific public slugs // i18n (optional) apiKey?: string; ns?: string[]; // namespaces to load on init translation?: I18nOptions["translation"]; // seed translations (no loader) fallbackLocale?: string | string[]; // default: same as defaultLocale defaultNs?: string; // default "default" devMode?: boolean; // default: NODE_ENV === "development" basicHtmlTags?: string[]; // tags allowed in tag interpolation onMissingKey?: I18nOptions["onMissingKey"]; // (info: MissingKeyInfo) => TranslationResult | void}Return Value
Section titled “Return Value”interface CreateNextI18nResult { i18n: I18n; // use with <I18nProvider> and setI18n() routing: Required<RoutingConfig>; // pass to createMiddleware() and <I18nProvider routing={...}> use(plugin: I18nPlugin, options?: PluginOptions): this; // resolved plugin, both runtimes useClient(plugin: I18nPlugin, options?: ScopedPluginOptions): this; // resolved, client only (typeof window !== 'undefined') useServer(plugin: I18nPlugin, options?: ScopedPluginOptions): this; // resolved, server only (NEXT_RUNTIME present) useClientLazy(loadPlugin: LazyPluginLoader, options?: ScopedPluginOptions): this; // dynamically imported, client only useServerLazy(loadPlugin: LazyPluginLoader, options?: ScopedPluginOptions): this; // dynamically imported, server only}
// loader resolves to a plugin function or a module with a default exporttype LazyPluginLoader = () => Promise<I18nPlugin | { default: I18nPlugin }>;
// scoped-plugin options extend the standard plugin optionsinterface ScopedPluginOptions extends PluginOptions { /** Where the plugin runs. @default "all" */ environment?: "all" | "development" | "production";}environment is how you gate a plugin to a build mode — e.g. register the in-context
editor plugin only in dev (environment: "development").
import { createNextI18n } from '@comvi/next';import { FetchLoader } from '@comvi/plugin-fetch-loader';
const nextI18n = createNextI18n({ locales: ['en', 'de'], defaultLocale: 'en',}) // useServerLazy takes a loader that resolves to a plugin .useServerLazy(() => import('@comvi/plugin-fetch-loader').then(m => m.FetchLoader({ cdnUrl: 'https://cdn.comvi.io/your-distribution-id' }), ), ) .useClientLazy( () => import('@comvi/plugin-in-context-editor').then(m => m.InContextEditorPlugin()), { environment: 'development', required: false }, );
export const { i18n, routing } = nextI18n;Server Component Utilities
Section titled “Server Component Utilities”setRequestLocale(locale)
Section titled “setRequestLocale(locale)”Store locale in async context for server functions. Call at the top of layout/page Server
Components — also required for static rendering with generateStaticParams(). In Next 15 the
params prop is a Promise, so await it:
import { setRequestLocale, getI18n } from '@comvi/next/server';
export default async function Layout({ params }: { params: Promise<{ locale: string }> }) { const { locale } = await params; setRequestLocale(locale); const { t } = await getI18n(); return <h1>{t('title')}</h1>;}getI18n(options?: GetI18nOptions)
Section titled “getI18n(options?: GetI18nOptions)”Access translation function in Server Components. Returns ServerI18n with t and hasTranslation. Async (may fetch for locale).
interface GetI18nOptions { locale?: string; // defaults to request locale ns?: string; // default namespace for t()}
interface ServerI18n { t: TranslationFunction; hasTranslation(key, options?: HasTranslationOptions): boolean;}
type TranslationFunction = (key: string, params?: TranslationParams) => string;
interface HasTranslationOptions { locale?: string; ns?: string;}getLocale()
Section titled “getLocale()”Get the current request locale (set by setRequestLocale or middleware).
import { getLocale } from '@comvi/next/server';
export default async function Layout({ children }) { const locale = await getLocale(); return <html lang={locale}>{children}</html>;}loadTranslations(locale, options?)
Section titled “loadTranslations(locale, options?)”Preload translations on the server before rendering. Returns a serializable map keyed by "locale:namespace" that you can pass into the client <I18nProvider> for hydration. Defaults to the default namespace; pass { namespaces: [...] } for more. If no loader is configured it returns whatever is already cached and warns once.
interface LoadTranslationsOptions { namespaces?: string[]; // default: [defaultNs]}
type TranslationsResult = Record<string, Record<string, TranslationValue>>;import { loadTranslations, setRequestLocale } from '@comvi/next/server';
export default async function Layout({ params, children,}: { params: Promise<{ locale: string }>; children: React.ReactNode;}) { const { locale } = await params; setRequestLocale(locale); const messages = await loadTranslations(locale, { namespaces: ['common', 'dashboard'], }); return <>{children}</>;}setI18n(i18n)
Section titled “setI18n(i18n)”Registers the i18n instance for the server entry points (getI18n, getLocale,
loadTranslations). Required — those helpers throw "[comvi/next] i18n not configured. Call setI18n(i18n) in your i18n configuration file." until it has run. The README pattern is a server-only side-effect module that calls it and re-exports the helpers:
import "server-only";import { setI18n } from "@comvi/next/server";import { i18n } from "./config";
setI18n(i18n);
export { i18n, routing } from "./config";export { getI18n, getLocale, loadTranslations } from "@comvi/next/server";Then import that module once at the top of your root layout (import "@/i18n/server";) so
the side effect runs before any Server Component calls getI18n(). The instance is held in a
module-level reference; Next.js gives the server and client bundles separate module copies, so
the client provider gets its own instance via <I18nProvider i18n={i18n}>.
Client Component Hooks
Section titled “Client Component Hooks”Client Components use the same hooks as @comvi/react. Import from @comvi/next/client — that is the only entry point that exports React-only code marked with "use client". Importing from @comvi/next (the main entry) does not expose useI18n, T, or I18nProvider.
'use client';import { useI18n } from '@comvi/next/client';
export function Counter() { const { t, locale, setLocale, isLoading } = useI18n();
return <button>{t('counter.increment')}</button>;}@comvi/next/client re-exports useI18n, useI18nContext, T, createI18n, and UseI18nReturn/TProps types from @comvi/react, plus a Next.js-aware <I18nProvider> (handles locale syncing for hydration) with its I18nProviderProps and MessagesMap types.
As of v0.3, the four selector hooks are also re-exported from @comvi/next/client: useLocale, useIsLoading, useSetLocaleTransition, and useFormatters. App Router Client Components can import them directly from @comvi/next/client without a separate @comvi/react import.
'use client';import { useLocale, useIsLoading, useSetLocaleTransition, useFormatters } from '@comvi/next/client';See the @comvi/react API Reference for the full signatures of these hooks.
<T> Component
Section titled “<T> Component”Same API as the React <T> component. Import from @comvi/next/client (re-exported from @comvi/react):
'use client';import { T } from '@comvi/next/client';| Prop | Type | Default | Description |
|---|---|---|---|
i18nKey | string | — | Translation key to resolve (required) |
params | Record<string, unknown> | {} | Interpolation parameters |
ns | string | — | Namespace to look up the key in |
locale | string | — | Specific locale to use |
fallback | string | — | Fallback text if the key is missing |
raw | boolean | — | Forward raw: true to post-processors that support it, such as the in-context editor marker injector |
components | Record<string, ComponentHandler> | — | Map of tag names to React elements or functions for rich-text interpolation |
children | ReactNode | — | Fallback content if translation is missing |
Middleware
Section titled “Middleware”The middleware handles locale detection, URL rewriting, and redirects for locale-prefixed routes. Use createMiddleware with your routing config:
import { createMiddleware } from '@comvi/next/middleware';import { routing } from './i18n';
export default createMiddleware(routing);
export const config = { matcher: ['/((?!api|_next|.*\\..*).*)'],};What the Middleware Does
Section titled “What the Middleware Does”- Runs your custom
detectLocale(request)callback, if configured (highest priority) - Reads the locale from the URL path (
/de/aboutextractsde) - Falls back to the configured
localeDetection.order— by defaultcookie, thenAccept-Language - Falls back to the default locale
- Handles URL prefix mode (
always,as-needed,never) and appliespathnamesmappings (redirect to the canonical public slug, then internal rewrite) - Sets the
x-comvi-localeheader for Server Components to read (viagetLocale()/getI18n()) - Persists the detected locale in a cookie (default name
NEXT_LOCALE;Secureis auto-disabled in dev)
MiddlewareConfig
Section titled “MiddlewareConfig”createMiddleware accepts your RoutingConfig (it is a superset of it) plus two optional
extras for customizing detection:
interface MiddlewareConfig extends RoutingConfig { // RoutingConfig fields: // locales: readonly string[] // defaultLocale: string // localePrefix?: "always" | "as-needed" | "never" // default "as-needed" // localeCookie?: string // default "NEXT_LOCALE" // pathnames?: Record<string, Partial<Record<string, string>>>
/** Highest-priority detector. Return undefined to fall through to URL / localeDetection. */ detectLocale?: (request: NextRequest) => string | undefined;
/** Tune the built-in detection sources. */ localeDetection?: LocaleDetectionConfig;}
interface LocaleDetectionConfig { /** Order of built-in sources (URL path is always checked first). @default ["cookie", "accept-language"] */ order?: Array<"cookie" | "header" | "accept-language">; /** Cookie name for the locale preference. @default "NEXT_LOCALE" (or routing's localeCookie) */ cookieName?: string; /** Set the Secure flag on the locale cookie. Auto-disabled in development. @default true */ cookieSecure?: boolean; /** Custom header to read locale from (only used if "header" is in `order`). */ headerName?: string; /** Custom Accept-Language resolver. @default a small built-in parser */ resolveAcceptLanguage?: ( acceptLanguage: string, locales: readonly string[], defaultLocale: string, ) => string | undefined;}import { createMiddleware } from '@comvi/next/middleware';import { routing } from './i18n';
export default createMiddleware({ ...routing, localeDetection: { order: ['header', 'cookie', 'accept-language'], headerName: 'x-user-locale', cookieName: 'MY_LOCALE', }, // Or bypass the built-ins entirely: // detectLocale: (request) => request.geo?.country === 'DE' ? 'de' : undefined,});For RFC-compliant Accept-Language matching (CJK, regional variants) plug in
@formatjs/intl-localematcher via resolveAcceptLanguage.
Custom Middleware
Section titled “Custom Middleware”If you need to combine Comvi’s middleware with your own logic, call the returned function:
import { createMiddleware } from '@comvi/next/middleware';import { routing } from './i18n';import type { NextRequest } from 'next/server';
const comviMiddleware = createMiddleware(routing);
export default function middleware(request: NextRequest) { // Your custom logic here // ...
return comviMiddleware(request);}
export const config = { matcher: ['/((?!api|_next|.*\\..*).*)'],};Navigation Utilities
Section titled “Navigation Utilities”Import from @comvi/next/navigation:
import { Link, usePathname, useLocalizedRouter } from '@comvi/next/navigation';<Link> Component
Section titled “<Link> Component”A locale-aware replacement for Next.js’s <Link> that automatically prefixes the href with the current locale:
import { Link } from '@comvi/next/navigation';
function Nav() { return ( <nav> <Link href="/about">About</Link> {/* When locale is "de", navigates to /de/about */}
<Link href="/contact" locale="fr">Contact (FR)</Link> {/* Always navigates to /fr/contact */} </nav> );}Extends all Next.js <Link> props with an optional locale prop to override the current locale.
usePathname()
Section titled “usePathname()”Returns the current pathname without the locale prefix:
'use client';import { usePathname } from '@comvi/next/navigation';
function Breadcrumb() { const pathname = usePathname(); // When URL is /de/about, pathname is "/about"
return <span>{pathname}</span>;}useLocalizedRouter()
Section titled “useLocalizedRouter()”Returns a router object that automatically prefixes paths with the current locale. Same API as Next.js’s useRouter() but locale-aware:
'use client';import { useLocalizedRouter } from '@comvi/next/navigation';
function Nav() { const router = useLocalizedRouter();
return ( <button onClick={() => router.push('/about')}> {/* When locale is "de", navigates to /de/about */} Go to About </button> );}LocalizedRouter methods:
| Method | Type | Description |
|---|---|---|
push | (href: string, locale?: string) => void | Navigate to a locale-prefixed path |
replace | (href: string, locale?: string) => void | Replace current URL with locale-prefixed path |
back | () => void | Navigate back in history |
forward | () => void | Navigate forward in history |
refresh | () => void | Refresh the current route |
prefetch | (href: string, locale?: string) => void | Prefetch a locale-prefixed path |
Routing Helpers
Section titled “Routing Helpers”Import from @comvi/next/routing (server-safe — no "use client"):
import { defineRouting, hasLocale, createGetPathname } from '@comvi/next/routing';defineRouting(config)
Section titled “defineRouting(config)”Normalizes a RoutingConfig (fills in localePrefix, localeCookie, pathnames defaults) and returns Required<RoutingConfig>. createNextI18n calls this for you and returns the result as routing; use defineRouting directly only if you keep routing config separate from the i18n instance.
import { defineRouting } from '@comvi/next/routing';
export const routing = defineRouting({ locales: ['en', 'uk', 'de'], defaultLocale: 'en', localePrefix: 'as-needed',});hasLocale(locales, value)
Section titled “hasLocale(locales, value)”Type guard that checks whether a string is one of locales and narrows the type. Use it to validate the locale route segment in layouts/pages:
import { hasLocale } from '@comvi/next/routing';import { notFound } from 'next/navigation';import { routing } from '@/i18n/routing';
export default async function Page({ params }: { params: Promise<{ locale: string }> }) { const { locale } = await params; if (!hasLocale(routing.locales, locale)) { notFound(); } // locale is now typed as 'en' | 'uk' | 'de'}createGetPathname(routing)
Section titled “createGetPathname(routing)”Returns a getPathname({ locale, href }) function bound to your routing config — applies localePrefix and pathnames rules. Use it for sitemaps, hreflang/canonical tags:
import { createGetPathname } from '@comvi/next/routing';import { routing } from '@/i18n/routing';
const getPathname = createGetPathname(routing);
export default function sitemap() { const baseUrl = 'https://example.com'; const pages = ['/', '/about', '/contact'];
return pages.flatMap((page) => routing.locales.map((locale) => ({ url: `${baseUrl}${getPathname({ locale, href: page })}`, lastModified: new Date(), alternates: { languages: Object.fromEntries( routing.locales.map((l) => [l, `${baseUrl}${getPathname({ locale: l, href: page })}`]), ), }, })), );}Static Generation
Section titled “Static Generation”Generate static pages for all supported locales:
import { routing } from '@/i18n/config';
export function generateStaticParams() { return routing.locales.map((locale) => ({ locale }));}Entry Points
Section titled “Entry Points”Imports are split by runtime to keep "use client" boundaries clean:
| Entry point | Use from | What it exports |
|---|---|---|
@comvi/next | Anywhere (shared/server config) | createNextI18n, createI18n, I18n, all @comvi/core types, plus types CreateNextI18nOptions, CreateNextI18nResult, RoutingConfig, LocalePrefixMode, MiddlewareConfig, GetI18nOptions, I18nProviderProps |
@comvi/next/server | Server Components and route handlers | setRequestLocale, getI18n, getLocale, setI18n, loadTranslations; types: GetI18nOptions, ServerI18n, TranslationFunction, HasTranslationOptions, LoadTranslationsOptions, TranslationsResult |
@comvi/next/client | Client Components ('use client') | useI18n, useI18nContext, useLocale, useIsLoading, useSetLocaleTransition, useFormatters, T, createI18n (from @comvi/react), Next-aware I18nProvider; types: UseI18nReturn, TProps, I18nProviderProps, MessagesMap |
@comvi/next/middleware | middleware.ts | createMiddleware; types: MiddlewareConfig, LocaleDetectionConfig, LocaleDetectionSource, ResolveAcceptLanguage |
@comvi/next/navigation | Client Components | Link, usePathname, useLocalizedRouter; types: LocalizedLinkProps, LocalizedRouter |
@comvi/next/routing | Anywhere (server-safe) | defineRouting, hasLocale, createGetPathname; types: RoutingConfig, LocalePrefixMode, GetPathnameOptions |
See the @comvi/react API Reference and @comvi/core API Reference for the underlying APIs.