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
  • How to fetch per-tenant brand colors server-side and inject them as CSS custom properties

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.

Creating a Universal API Key
anchor

A universal API key must be provisioned programmatically via the ApiKeyFactory extension. This factory runs when Webiny boots and ensures the key exists in the root tenant with the permissions you specify.

Create the factory file:

extensions/security/universalApiKey.ts

Register it in webiny.config.tsx:

webiny.config.tsx

Set the token value as NEXT_PUBLIC_WEBSITE_BUILDER_API_KEY in your Next.js environment:

Token security

Store the token value in a secret manager and inject it at deploy time. Do not commit the raw token to source control. The permissions: [{ name: "*" }] grant gives full read access across all tenants — use a more restrictive permission set if your deployment requires it.

Per-Tenant Theming
anchor

Each tenant can carry its own brand colors stored as custom fields on the Webiny tenant model (e.g. primaryColor, secondaryColor). The recommended pattern is to fetch them server-side in your page component using @webiny/sdk, then inject them into <head> as CSS custom properties — available on the very first render with no client-side flash.

Initialize the Webiny SDK
anchor

Create a singleton Sdk instance using the same environment variables as the Website Builder SDK:

src/lib/webiny.ts

Fetch and Inject Theme Colors
anchor

Add a server-side helper that calls sdk.tenantManager.getCurrentTenant(), reads the custom extensions.theme object, and builds a :root CSS rule:

Call it in parallel with your page fetch and inject the result as a <style> tag:

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

Use the CSS Variables in Components
anchor

Any client component can now reference the variables directly — no props, no context:

The custom properties are set on :root before any React hydration, so the correct tenant colors are always visible on first paint.

Tenant model extensions

The extensions.theme object is populated via a custom tenant model extension in Webiny Admin. The field names (primaryColor, secondaryColor, etc.) depend on how your extension is defined. Cast values.extensions to your own typed interface when reading it.