Beta V4 NextJS SDK - Unable to Detect Host Before Initializing Auth0Client

I am attempting to implement the nextjs-auth0 SDK on a NextJS application using Next 15. I am familiar with the v3 SDK but am having to use the beta v4 SDK because of the NextJS version on the UI I am building.

The problem I’m running into is this: In v3, auth0 client initialization happened in an api endpoint and hence could receive a request object. This was allowing me to use the same NextJS repo and deployment to serve multiple brand units from separate Auth0 tenants/applications, all through the dynamic detection of the brand via req.headers.host. Under the v4 SDK, the auth0 client is loaded at app startup rather than during an actual request, preventing the reading of a host header.

I have attempted to delay this client creation until I have access to a request but end up running into issues with middleware.ts. Is there some mechanism by which I can prevent Auth0Client initialization until a host URL can be read without tanking the entire setup?

Okay I have found a functioning workaround for this problem. Rather than storing the Auth0Client initialization in /lib/auth0.ts, you can move it to the middleware function in middleware.ts like so…

import type { NextRequest } from "next/server"
import { Auth0Client } from "@auth0/nextjs-auth0/server";
import { detectBusinessUnitFromRequest } from "@/app/helpers/detectBusinessUnitFromRequest";

let auth0: Auth0Client;

export async function middleware(request: NextRequest) {
    const businessUnit = detectBusinessUnitFromRequest(request);
    const optionsConfig = {
        domain:
            process.env[`${businessUnit.toUpperCase()}_AUTH0_DOMAIN`] ||
            process.env.AUTH0_DOMAIN,
        clientId:
            process.env[`${businessUnit.toUpperCase()}_AUTH0_CLIENT_ID`] ||
            process.env.AUTH0_CLIENT_ID,
        clientSecret:
            process.env[`${businessUnit.toUpperCase()}_AUTH0_CLIENT_SECRET`] ||
            process.env.AUTH0_CLIENT_SECRET,
        appBaseUrl:
            process.env[`${businessUnit.toUpperCase()}_APP_BASE_URL`] ||
            process.env.APP_BASE_URL,
        secret:
            process.env[`${businessUnit.toUpperCase()}_AUTH0_SECRET`] ||
            process.env.AUTH0_SECRET,
    };
    auth0 = new Auth0Client(optionsConfig)
    return await auth0.middleware(request)
}

// @ts-expect-error - auth0 will be defined at runtime so we don't need this error
export default auth0 as Auth0Client;
export const config = {
    matcher: ["/((?!_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)"],
}

I’m not a big fan of having to use an //@ts-expect-error here. I would love to hear anyone’s ideas for a more elegant solution.

Okay I’m just keeping this updated in case anyone else runs into the same requirement. Here’s my best solution so far. Omit the lib/auth0.ts file entirely. Instead, create a helper method such as…

import { Auth0Client } from "@auth0/nextjs-auth0/server";

const createAuth0Client = async (brand: string): Promise<Auth0Client> => {
    const optionsConfig = {
        domain:
            process.env[`${brand.toUpperCase()}_AUTH0_DOMAIN`] ||
            process.env.AUTH0_DOMAIN as string,
        clientId:
            process.env[`${brand.toUpperCase()}_AUTH0_CLIENT_ID`] ||
            process.env.AUTH0_CLIENT_ID as string,
        clientSecret:
            process.env[`${brand.toUpperCase()}_AUTH0_CLIENT_SECRET`] ||
            process.env.AUTH0_CLIENT_SECRET as string,
        appBaseUrl:
            process.env[`${brand.toUpperCase()}_APP_BASE_URL`] ||
            process.env.APP_BASE_URL as string,
        secret:
            process.env[`${brand.toUpperCase()}_AUTH0_SECRET`] ||
            process.env.AUTH0_SECRET as string,
    };
    return new Auth0Client(optionsConfig)
}

export default createAuth0Client;

Then in middleware.ts:

import type { NextRequest } from "next/server"

import { detectBusinessUnitFromRequest } from "@/app/helpers/detectBusinessUnitFromRequest";
import createAuth0Client from "@/app/helpers/createAuth0Client";

export async function middleware(request: NextRequest) {
    const businessUnit = detectBusinessUnitFromRequest(request);
    const auth0 = await createAuth0Client(businessUnit);
    return await auth0.middleware(request)
}


export const config = {
    matcher: ["/((?!_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)"],
}

And in a server component:

import { NextRequest } from "next/server";
import { detectBusinessUnitFromRequest } from "@/app/helpers/detectBusinessUnitFromRequest";
import createAuth0Client from "@/app/helpers/createAuth0Client";
import Auth0SessionObj from "@/app/types/Auth0SessionObj";

export const dynamic = 'force-dynamic';
export async function GET(request: NextRequest) {
     const brand = detectBusinessUnitFromRequest(request);
     const auth0 = await createAuth0Client(brand);
     const { user, tokenSet } = await auth0.getSession() as Auth0SessionObj;
     // whatever other api logic you want...
}