WHAT YOU'LL LEARN
  • How a single Next.js app can serve content from multiple Webiny tenants
  • How to resolve tenant identity from the incoming request
  • How to initialize the SDK with the correct tenant per request
  • Which API key strategy fits multi-tenant deployments

Overview
anchor

The default Next.js starter kit connects to one Webiny tenant set by the NEXT_PUBLIC_WEBSITE_BUILDER_API_TENANT environment variable. For SaaS platforms and multi-tenant deployments, a single Next.js instance can serve content from many tenants by resolving tenant identity at request time.

The pattern has three parts: resolve tenant identity in middleware, propagate it to server components via a request header, then read it when initializing the SDK.

How Tenant Scoping Works
anchor

The contentSdk.init() call accepts an apiTenant parameter. Normally this is set from the environment variable, but you can pass it dynamically. The SDK then scopes all API calls — page fetching, redirect resolution — to that tenant.

Resolving Tenant in Middleware
anchor

Next.js middleware runs before any rendering and is the right place to resolve tenant identity from the request — subdomain, full domain, path prefix, or any other signal — and attach it as a header for downstream components.

Create or update src/middleware.ts:

src/middleware.ts

The wb.tenant check handles editor traffic: when the Website Builder editor loads your Next.js app in an iframe, it passes the current tenant via this query parameter. Your domain-based resolution runs as a fallback for public visitors.

Tenant Resolution Strategy

The example above uses subdomain-based resolution. Replace resolveTenantFromHostname with any strategy that fits your routing — full domain matching against a lookup table, a path prefix, a cookie, or a JWT claim.

Reading Tenant in Server Components
anchor

Add a utility that reads the X-Tenant header set by middleware:

src/contentSdk/getTenant.ts

The fallback to "root" ensures the app keeps working when no tenant header is present, for example during static generation at build time.

Initializing the SDK per Request
anchor

Update initializeContentSdk to accept a tenantId parameter. When provided, it overrides the NEXT_PUBLIC_WEBSITE_BUILDER_API_TENANT environment variable:

src/contentSdk/initializeContentSdk.ts

Call getTenant() and pass the result wherever you initialize the SDK — in generateStaticParams, generateMetadata, and the page render function:

src/app/[[...slug]]/page.tsx

The root layout passes the tenant to the client-side ContentSdkInitializer component:

src/app/layout.tsx

ContentSdkInitializer is a memoized client component that calls initializeContentSdk on the browser side with the same tenant context.

API Key Strategy
anchor

Two approaches work for multi-tenant deployments:

Universal key — Create one API key at the root tenant level with read access across all tenants. Set it as NEXT_PUBLIC_WEBSITE_BUILDER_API_KEY. The apiTenant parameter already scopes all API calls to the correct tenant, so a single key is sufficient for most cases.

Per-tenant keys — Each tenant has its own Website Builder API key (auto-created by the platform under Settings → Access Management → API Keys). You resolve the key per request by fetching it from your own data store or the Webiny API. This adds per-request key resolution overhead but provides stronger isolation at the credential level.

For most SaaS deployments, a universal key is the simpler and recommended starting point.

Per-Tenant Theming
anchor

The Website Builder supports per-tenant theming — each tenant can have its own fonts, colors, and CSS variables managed through Webiny Admin. In the root layout, fetch the tenant’s theme configuration from the Webiny API and merge it with your static base theme before passing it to ContentSdkInitializer and injecting it into <head>.

The per-tenant theme data (font family, primary color, etc.) is stored as custom fields on the tenant model in Webiny and fetched via GraphQL with the X-Tenant header.