Next.js
`@multitenant/next`, `next-app`, `next-pages` — meta package, App Router, Pages Router.
Next.js — Meta package
Meta-package for App Router apps: re-exports @multitenant/core, @multitenant/config, @multitenant/react, and @multitenant/next-app so you can depend on one scope for typical Next.js setups.
Install
npm install @multitenant/next next react react-domWhen to use
- Prefer
@multitenant/nextwhen you want one install line and tree-shaking still keeps bundles reasonable. - Use
@multitenant/next-appalone if you only need the middleware/server surface and already depend on core separately.
Next.js — App Router
Next.js App Router integration: middleware for Edge-safe tenant resolution (headers), plus server helpers to read x-tenant-key, x-market-key, and related headers. Subpaths /auto and /auto-node re-export config-aware shortcuts.
Use @multitenant/next-app (or @multitenant/next). Keep middleware Edge-safe (static JSON / small bundle); put DB in Route Handlers, Server Actions, or RSC with runtime = 'nodejs' when needed.
Install
npm install @multitenant/next-app next react react-domPeers: next, and typically @multitenant/core for createTenantRegistry.
Main APIs
createTenantMiddleware(registry, options?)— sets resolution headers;onMissingTenant:'passthrough'|'warn'|'throw'getTenantFromHeaders,requireTenant— in Server Components / Route Handlers /nodejsruntimecachedFetch<T>(url, { locale, ... })— build-time request cache to reduce redundant CMS API calls across localescreateTenantCachedFetch<T>(registry, tenantKey, options?)— cached fetch pre-bound to a tenant's locale
Resolution vs data access
| Runtime | Tenant resolution | Database / heavy Node |
|---|---|---|
| Edge middleware | createTenantMiddleware + static config | No |
| Node server | getTenantFromHeaders / requireTenant | Yes — e.g. runWithTenantScope |
Middleware defaults
onMissingTenant: 'passthrough'— avoids throwing on rawlocalhostduringnext dev; usemultitenant dev+domains.localhosts, or'throw'when every request must resolve.- Prefer
x-forwarded-hostbehind a reverse proxy (middleware normalizes the left-most host). - Headers forwarded to the app:
x-tenant-key,x-market-key,x-tenant-env, optionalx-tenant-flags(JSON).
Shared registry module
lib/tenant-registry.ts
import type { TenantsConfig } from '@multitenant/core';
import { createTenantRegistry } from '@multitenant/core';
import tenantsConfig from '../tenants.config.json';
export const tenantRegistry = createTenantRegistry(tenantsConfig as TenantsConfig);
export function multitenantEnv() {
return (process.env.MULTITENANT_ENV ?? process.env.TENANTIFY_ENV ?? 'local') as import('@multitenant/core').EnvironmentName;
}middleware.ts
import { createTenantMiddleware } from '@multitenant/next-app';
import { multitenantEnv, tenantRegistry } from '@/lib/tenant-registry';
export const middleware = createTenantMiddleware(tenantRegistry, {
environment: multitenantEnv(),
});
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)'],
};Route Handler
import { headers } from 'next/headers';
import { NextResponse } from 'next/server';
import { getTenantFromHeaders } from '@multitenant/next-app';
import { multitenantEnv, tenantRegistry } from '@/lib/tenant-registry';
export async function GET() {
const h = await headers();
const resolved = getTenantFromHeaders(h, tenantRegistry, { environment: multitenantEnv() });
return NextResponse.json({
tenantKey: resolved?.tenantKey ?? null,
marketKey: resolved?.marketKey ?? null,
});
}Server Action
'use server';
import { headers } from 'next/headers';
import { getTenantFromHeaders } from '@multitenant/next-app';
import { multitenantEnv, tenantRegistry } from '@/lib/tenant-registry';
export async function currentTenantKey(): Promise<string | null> {
const h = await headers();
const resolved = getTenantFromHeaders(h, tenantRegistry, { environment: multitenantEnv() });
return resolved?.tenantKey ?? null;
}Use requireTenant when a missing tenant must hard-fail — it throws TenantNotFoundError.
Server Actions & Node-only code
Set export const runtime = 'nodejs' on routes/layouts that use fs, native drivers, or @multitenant/database.
Zero-config shortcut
@multitenant/next-app/auto — createTenantMiddlewareFromConfig(json, { environment }) for a single-file middleware (see Examples — Next.js (App Router)).
Next.js — Pages Router
Next.js Pages Router helpers: getServerSideProps and API route wrappers that attach ResolvedTenant (and market) to the request context.
You typically createTenantRegistry once (e.g. lib/tenant-registry.ts) and pass registry + environment into helpers.
Install
npm install @multitenant/next-pages next react react-domPackage name on npm is next-pages (monorepo folder next-paages).
Main APIs
| Export | Role |
|---|---|
withTenantGSSP | Wrap getServerSideProps — receives context with resolvedTenant (or triggers notFound) |
withTenantApi | Wrap NextApiHandler — req.tenant pattern or JSON errors |
Behaviour when host does not resolve
| API | Result |
|---|---|
withTenantApi | 404 response { error, code: 'MULTITENANT_TENANT_NOT_FOUND' } |
withTenantGSSP | { notFound: true } — Next notFound(); no TenantNotFoundError thrown inside GSSP |
See Errors.
Scaffold
npx @multitenant/cli init --framework next-pagesInstall @multitenant/next-pages, next, react, @multitenant/core.
For the App Router, see Next.js — App Router above.
Build-Time Request Cache (App Router)
For multi-locale builds, @multitenant/next-app includes a built-in cache for fetch requests during next build. This dramatically reduces redundant API calls to Contentful, Sanity, Stripe, and other external services.
Quick Example
import { cachedFetch } from '@multitenant/next-app';
export async function generateStaticParams() {
const data = await cachedFetch<PagesData>(
'https://api.contentful.com/pages',
{ locale: 'en-US', debug: true },
);
return data.pages.map((p) => ({ slug: p.slug }));
}How It Works
- During
next build: check cache → on miss: fetch → store to.next/.build-cache/ - During
next dev/ runtime: pass through to nativefetch()(no caching) - CLI:
multitenant cache --statsto view entries;multitenant cache --locale allto clear
Why It Helps
Multi-locale builds without cache:
- Build 4 locales × 10 endpoints = 40 API calls
- Rate limits exhausted
- Longer builds
With cache:
- First locale: fetch all 10 endpoints
- Next 3 locales: reuse cached data
- Result: 13 API calls (70% reduction)
APIs
| Export | Use case |
|---|---|
cachedFetch<T>(url, { locale, debug?, ... }) | Simple per-locale caching |
createTenantCachedFetch<T>(registry, tenantKey, options?) | Pre-bound to tenant's market locale; override per-call |
getCacheStats() | Observe hit/miss counts |
Learn More
Full guide, performance notes, invalidation strategies, and examples: Build-Time Cache.
See also
@multitenant/react— client context from the same headers as App Router- Build-Time Cache guide — detailed API & examples
- Home — adapters & package map