Skip to content

@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.

Creates an i18n instance with Next.js-specific configuration. Extends the standard createI18n options with server-side and routing settings.

src/i18n.ts
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',
}));
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
}
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 export
type LazyPluginLoader = () => Promise<I18nPlugin | { default: I18nPlugin }>;
// scoped-plugin options extend the standard plugin options
interface 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;

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>;
}

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;
}

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>;
}

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}</>;
}

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:

i18n/server.ts
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 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.

src/components/Counter.tsx
'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.

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';
PropTypeDefaultDescription
i18nKeystringTranslation key to resolve (required)
paramsRecord<string, unknown>{}Interpolation parameters
nsstringNamespace to look up the key in
localestringSpecific locale to use
fallbackstringFallback text if the key is missing
rawbooleanForward raw: true to post-processors that support it, such as the in-context editor marker injector
componentsRecord<string, ComponentHandler>Map of tag names to React elements or functions for rich-text interpolation
childrenReactNodeFallback content if translation is missing

The middleware handles locale detection, URL rewriting, and redirects for locale-prefixed routes. Use createMiddleware with your routing config:

src/middleware.ts
import { createMiddleware } from '@comvi/next/middleware';
import { routing } from './i18n';
export default createMiddleware(routing);
export const config = {
matcher: ['/((?!api|_next|.*\\..*).*)'],
};
  1. Runs your custom detectLocale(request) callback, if configured (highest priority)
  2. Reads the locale from the URL path (/de/about extracts de)
  3. Falls back to the configured localeDetection.order — by default cookie, then Accept-Language
  4. Falls back to the default locale
  5. Handles URL prefix mode (always, as-needed, never) and applies pathnames mappings (redirect to the canonical public slug, then internal rewrite)
  6. Sets the x-comvi-locale header for Server Components to read (via getLocale() / getI18n())
  7. Persists the detected locale in a cookie (default name NEXT_LOCALE; Secure is auto-disabled in dev)

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;
}
src/middleware.ts
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.

If you need to combine Comvi’s middleware with your own logic, call the returned function:

src/middleware.ts
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|.*\\..*).*)'],
};

Import from @comvi/next/navigation:

import { Link, usePathname, useLocalizedRouter } from '@comvi/next/navigation';

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.

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>;
}

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:

MethodTypeDescription
push(href: string, locale?: string) => voidNavigate to a locale-prefixed path
replace(href: string, locale?: string) => voidReplace current URL with locale-prefixed path
back() => voidNavigate back in history
forward() => voidNavigate forward in history
refresh() => voidRefresh the current route
prefetch(href: string, locale?: string) => voidPrefetch a locale-prefixed path

Import from @comvi/next/routing (server-safe — no "use client"):

import { defineRouting, hasLocale, createGetPathname } from '@comvi/next/routing';

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.

i18n/routing.ts
import { defineRouting } from '@comvi/next/routing';
export const routing = defineRouting({
locales: ['en', 'uk', 'de'],
defaultLocale: 'en',
localePrefix: 'as-needed',
});

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'
}

Returns a getPathname({ locale, href }) function bound to your routing config — applies localePrefix and pathnames rules. Use it for sitemaps, hreflang/canonical tags:

app/sitemap.ts
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 })}`]),
),
},
})),
);
}

Generate static pages for all supported locales:

src/app/[locale]/page.tsx
import { routing } from '@/i18n/config';
export function generateStaticParams() {
return routing.locales.map((locale) => ({ locale }));
}

Imports are split by runtime to keep "use client" boundaries clean:

Entry pointUse fromWhat it exports
@comvi/nextAnywhere (shared/server config)createNextI18n, createI18n, I18n, all @comvi/core types, plus types CreateNextI18nOptions, CreateNextI18nResult, RoutingConfig, LocalePrefixMode, MiddlewareConfig, GetI18nOptions, I18nProviderProps
@comvi/next/serverServer Components and route handlerssetRequestLocale, getI18n, getLocale, setI18n, loadTranslations; types: GetI18nOptions, ServerI18n, TranslationFunction, HasTranslationOptions, LoadTranslationsOptions, TranslationsResult
@comvi/next/clientClient Components ('use client')useI18n, useI18nContext, useLocale, useIsLoading, useSetLocaleTransition, useFormatters, T, createI18n (from @comvi/react), Next-aware I18nProvider; types: UseI18nReturn, TProps, I18nProviderProps, MessagesMap
@comvi/next/middlewaremiddleware.tscreateMiddleware; types: MiddlewareConfig, LocaleDetectionConfig, LocaleDetectionSource, ResolveAcceptLanguage
@comvi/next/navigationClient ComponentsLink, usePathname, useLocalizedRouter; types: LocalizedLinkProps, LocalizedRouter
@comvi/next/routingAnywhere (server-safe)defineRouting, hasLocale, createGetPathname; types: RoutingConfig, LocalePrefixMode, GetPathnameOptions

See the @comvi/react API Reference and @comvi/core API Reference for the underlying APIs.