From aacdb39a9a363417b78280dd12a35ecb636c45d4 Mon Sep 17 00:00:00 2001 From: vklimontovich Date: Wed, 3 Jun 2026 18:06:56 -0400 Subject: [PATCH 1/4] fix: deliver client-side templates to Pages Router clients Pages Router's getNextlyticsProps runs in getInitialProps without access to config, so it can't collect client-side templates the way App Router's NextlyticsServer does. Include templates in the /api/event response and merge them on the client from whichever source supplies them, so script insertions compile on both routers. Also stop getNextlyticsProps from throwing on client-side navigations, where _app's getInitialProps re-runs with no req. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/core/src/api-handler.ts | 18 +++++++++++++---- packages/core/src/client.tsx | 33 +++++++++++++++++++++++++++---- packages/core/src/middleware.ts | 6 ++++-- packages/core/src/pages-router.ts | 14 +++++++++++-- packages/core/src/server.tsx | 7 ++++++- packages/core/src/types.ts | 3 +++ 6 files changed, 68 insertions(+), 13 deletions(-) diff --git a/packages/core/src/api-handler.ts b/packages/core/src/api-handler.ts index f51f91d..2184739 100644 --- a/packages/core/src/api-handler.ts +++ b/packages/core/src/api-handler.ts @@ -8,6 +8,7 @@ import type { ClientContext, ClientRequest, DispatchResult, + JavascriptTemplate, PageViewDelivery, NextlyticsEvent, RequestContext, @@ -30,6 +31,9 @@ export type UpdateEvent = ( ctx: RequestContext ) => Promise; +/** Collect the client-side templates from the configured backends. */ +export type CollectTemplates = (ctx: RequestContext) => Record; + type HandlerContext = { pageRenderId: string; isSoftNavigation: boolean; @@ -39,6 +43,7 @@ type HandlerContext = { config: NextlyticsConfigWithDefaults; dispatchEvent: DispatchEvent; updateEvent: UpdateEvent; + collectTemplates: CollectTemplates; }; function createRequestContext(request: NextRequest): RequestContext { @@ -118,6 +123,7 @@ async function handleClientInit( config, dispatchEvent, updateEvent, + collectTemplates, } = hctx; const { clientContext } = request; const serverContext = reconstructServerContext(apiCallServerContext, clientContext); @@ -158,6 +164,7 @@ async function handleClientInit( return Response.json({ ok: true, items: filterScripts(actions), + templates: collectTemplates(ctx), }); } @@ -167,14 +174,15 @@ async function handleClientInit( after(() => completion); after(() => updateEvent(pageRenderId, { clientContext, userContext, anonymousUserId }, ctx)); - return Response.json({ ok: true }); + return Response.json({ ok: true, templates: collectTemplates(ctx) }); } async function handleClientEvent( request: Extract, hctx: HandlerContext ): Promise { - const { pageRenderId, ctx, apiCallServerContext, userContext, config, dispatchEvent } = hctx; + const { pageRenderId, ctx, apiCallServerContext, userContext, config, dispatchEvent, collectTemplates } = + hctx; const { clientContext, name, props, collectedAt } = request; const serverContext = clientContext @@ -208,14 +216,15 @@ async function handleClientEvent( const actions = await clientActions; after(() => completion); - return Response.json({ ok: true, items: filterScripts(actions) }); + return Response.json({ ok: true, items: filterScripts(actions), templates: collectTemplates(ctx) }); } export async function handleEventPost( request: NextRequest, config: NextlyticsConfigWithDefaults, dispatchEvent: DispatchEvent, - updateEvent: UpdateEvent + updateEvent: UpdateEvent, + collectTemplates: CollectTemplates ): Promise { const softNavHeader = request.headers.get(analyticsHeaders.isSoftNavigation); const isSoftNavigation = softNavHeader === "1"; @@ -252,6 +261,7 @@ export async function handleEventPost( config, dispatchEvent, updateEvent, + collectTemplates, }; const bodyType = body.type; diff --git a/packages/core/src/client.tsx b/packages/core/src/client.tsx index da963f3..c6fd653 100644 --- a/packages/core/src/client.tsx +++ b/packages/core/src/client.tsx @@ -9,6 +9,7 @@ import { useMemo, useReducer, useRef, + useState, } from "react"; import { useNavigation, debug, InjectScript, type InjectScriptProps } from "./client-utils"; import type { @@ -251,9 +252,9 @@ async function sendEventToServer( return { ok: false }; } - // Parse response to get scripts + // Parse response to get scripts (and templates, for Pages Router clients) const data = await response.json().catch(() => ({ ok: response.ok })); - return { ok: data.ok ?? response.ok, items: data.items }; + return { ok: data.ok ?? response.ok, items: data.items, templates: data.templates }; } catch (error) { if (error instanceof Error && error.name === "AbortError") { return { ok: false }; @@ -264,12 +265,35 @@ async function sendEventToServer( } export function NextlyticsClient(props: { ctx: NextlyticsContext; children?: ReactNode }) { - const { requestId, scripts: initialScripts = [], templates = {} } = props.ctx; + const { requestId, scripts: initialScripts = [] } = props.ctx; // Refs for dynamic scripts (from sendEvent calls) - stable, no re-renders const scriptsRef = useRef[]>([]); const subscribersRef = useRef void>>(new Set()); + // Templates can arrive from two places: the ctx prop (App Router's + // NextlyticsServer collects them from config) and the /api/event response + // (Pages Router, where getNextlyticsProps has no access to config). Hold them + // in state and merge from whichever source supplies them, so they survive + // client-side navigations regardless of router. + const [templates, setTemplates] = useState>( + () => props.ctx.templates ?? {} + ); + const mergeTemplates = useCallback((incoming?: Record) => { + if (!incoming) return; + const keys = Object.keys(incoming); + if (keys.length === 0) return; + setTemplates((prev) => { + const hasNew = keys.some((k) => prev[k] !== incoming[k]); + return hasNew ? { ...prev, ...incoming } : prev; + }); + }, []); + + // Merge templates supplied via the ctx prop (App Router). + useEffect(() => { + mergeTemplates(props.ctx.templates); + }, [props.ctx.templates, mergeTemplates]); + const addScripts = useCallback((newScripts: TemplatizedScriptInsertion[]) => { debug("Adding scripts", { newCount: newScripts.length, @@ -293,8 +317,9 @@ export function NextlyticsClient(props: { ctx: NextlyticsContext; children?: Rea requestId, { type: "page-view", clientContext, softNavigation: softNavigation || undefined }, { signal, isSoftNavigation: softNavigation } - ).then(({ items }) => { + ).then(({ items, templates: responseTemplates }) => { debug("page-view response", { scriptsCount: items?.length ?? 0 }); + mergeTemplates(responseTemplates); if (items?.length) addScripts(items); }); }); diff --git a/packages/core/src/middleware.ts b/packages/core/src/middleware.ts index 0ba2604..6a9a9a2 100644 --- a/packages/core/src/middleware.ts +++ b/packages/core/src/middleware.ts @@ -19,6 +19,7 @@ import { handleEventPost, getUserContext, getEventProps, + type CollectTemplates, type DispatchEvent, type UpdateEvent, } from "./api-handler"; @@ -34,7 +35,8 @@ function createRequestContext(request: NextRequest): RequestContext { export function createNextlyticsMiddleware( config: NextlyticsConfigWithDefaults, dispatchEvent: DispatchEvent, - updateEvent: UpdateEvent + updateEvent: UpdateEvent, + collectTemplates: CollectTemplates ): NextMiddleware { const { eventEndpoint } = config; @@ -74,7 +76,7 @@ export function createNextlyticsMiddleware( // Handle event endpoint directly in middleware if (pathname === eventEndpoint) { if (request.method === "POST") { - return handleEventPost(request, config, dispatchEvent, updateEvent); + return handleEventPost(request, config, dispatchEvent, updateEvent, collectTemplates); } return Response.json({ error: "Method not allowed" }, { status: 405 }); } diff --git a/packages/core/src/pages-router.ts b/packages/core/src/pages-router.ts index c259bb5..7061536 100644 --- a/packages/core/src/pages-router.ts +++ b/packages/core/src/pages-router.ts @@ -2,16 +2,26 @@ import type { NextlyticsContext } from "./client"; import { restoreServerComponentContext } from "./server-component-context"; export type PagesRouterContext = { - req: { headers: Record; cookies?: Record }; + req?: { headers: Record; cookies?: Record }; }; /** * Get Nextlytics props for Pages Router _app.tsx. * Reads context from headers set by middleware. + * + * `_app`'s getInitialProps re-runs on every client-side navigation, where there + * is no `req`. Return an empty context in that case rather than throwing — the + * client already has its scripts and templates from the initial render and the + * /api/event round-trip. */ export function getNextlyticsProps(ctx: PagesRouterContext): NextlyticsContext { + const reqHeaders = ctx?.req?.headers; + if (!reqHeaders) { + return { requestId: "" }; + } + const headersList = new Headers(); - for (const [key, value] of Object.entries(ctx.req.headers)) { + for (const [key, value] of Object.entries(reqHeaders)) { if (value) { headersList.set(key, Array.isArray(value) ? value[0] : value); } diff --git a/packages/core/src/server.tsx b/packages/core/src/server.tsx index f6a2e33..53625d5 100644 --- a/packages/core/src/server.tsx +++ b/packages/core/src/server.tsx @@ -235,7 +235,12 @@ export function Nextlytics(userConfig: NextlyticsConfig): NextlyticsResult { return updateEventInternal(eventId, patch, ctx); }; - const middleware = createNextlyticsMiddleware(config, dispatchEventInternal, updateEventInternal); + const middleware = createNextlyticsMiddleware( + config, + dispatchEventInternal, + updateEventInternal, + (ctx) => collectTemplates(config, ctx) + ); /** Server component that provides analytics context to the app */ async function Server({ children }: { children: ReactNode }) { diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 0632fa3..e3260c7 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -292,6 +292,9 @@ export type ClientRequest = export type ClientRequestResult = { ok: boolean; items?: ClientActionItem[]; + /** Client-side templates from the backends, so Pages Router clients (which + * can't read them from config) can compile the script insertions. */ + templates?: Record; }; /** Return value from Nextlytics() */ From d76ac73ff2616362ff43f499be7fc3a73971683e Mon Sep 17 00:00:00 2001 From: vklimontovich Date: Wed, 3 Jun 2026 18:27:05 -0400 Subject: [PATCH 2/4] perf: only return templates the client is missing The client now sends the template ids it already holds in a header, and the server diffs against the templates resolved for the request, returning only the new ones. App Router clients hold the full set from the ctx prop, so they get an empty response; a Pages Router client receives each template once instead of on every event. The diff is per-request, so it stays correct when backend resolution (and thus the template set) varies by request. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/core/src/api-handler.ts | 42 ++++++++++++-- packages/core/src/client.tsx | 58 ++++++++++++++----- packages/core/src/server-component-context.ts | 3 + 3 files changed, 84 insertions(+), 19 deletions(-) diff --git a/packages/core/src/api-handler.ts b/packages/core/src/api-handler.ts index 2184739..3a285e2 100644 --- a/packages/core/src/api-handler.ts +++ b/packages/core/src/api-handler.ts @@ -44,8 +44,25 @@ type HandlerContext = { dispatchEvent: DispatchEvent; updateEvent: UpdateEvent; collectTemplates: CollectTemplates; + /** Template ids the client already holds (from the known-templates header). */ + knownTemplateIds: Set; }; +/** + * Collect the templates for this request and drop the ones the client already + * has. App Router clients already hold the full set (from the ctx prop), so they + * get nothing back; a Pages Router client receives each template once. Returns + * undefined when there is nothing new, to keep it out of the JSON response. + */ +function newTemplatesFor(hctx: HandlerContext): Record | undefined { + const all = hctx.collectTemplates(hctx.ctx); + const missing: Record = {}; + for (const [id, template] of Object.entries(all)) { + if (!hctx.knownTemplateIds.has(id)) missing[id] = template; + } + return Object.keys(missing).length > 0 ? missing : undefined; +} + function createRequestContext(request: NextRequest): RequestContext { return { headers: request.headers, @@ -123,7 +140,6 @@ async function handleClientInit( config, dispatchEvent, updateEvent, - collectTemplates, } = hctx; const { clientContext } = request; const serverContext = reconstructServerContext(apiCallServerContext, clientContext); @@ -164,7 +180,7 @@ async function handleClientInit( return Response.json({ ok: true, items: filterScripts(actions), - templates: collectTemplates(ctx), + templates: newTemplatesFor(hctx), }); } @@ -174,15 +190,14 @@ async function handleClientInit( after(() => completion); after(() => updateEvent(pageRenderId, { clientContext, userContext, anonymousUserId }, ctx)); - return Response.json({ ok: true, templates: collectTemplates(ctx) }); + return Response.json({ ok: true, templates: newTemplatesFor(hctx) }); } async function handleClientEvent( request: Extract, hctx: HandlerContext ): Promise { - const { pageRenderId, ctx, apiCallServerContext, userContext, config, dispatchEvent, collectTemplates } = - hctx; + const { pageRenderId, ctx, apiCallServerContext, userContext, config, dispatchEvent } = hctx; const { clientContext, name, props, collectedAt } = request; const serverContext = clientContext @@ -216,7 +231,11 @@ async function handleClientEvent( const actions = await clientActions; after(() => completion); - return Response.json({ ok: true, items: filterScripts(actions), templates: collectTemplates(ctx) }); + return Response.json({ + ok: true, + items: filterScripts(actions), + templates: newTemplatesFor(hctx), + }); } export async function handleEventPost( @@ -244,6 +263,16 @@ export async function handleEventPost( const apiCallServerContext = createServerContext(request); const userContext = await getUserContext(config, ctx); + const knownTemplatesHeader = request.headers.get(analyticsHeaders.knownTemplates); + const knownTemplateIds = new Set( + knownTemplatesHeader + ? knownTemplatesHeader + .split(",") + .map((id) => id.trim()) + .filter(Boolean) + : [] + ); + const cookiePageRenderId = request.cookies.get(LAST_PAGE_RENDER_ID_COOKIE)?.value; const pageRenderId = isSoftNavigation ? (cookiePageRenderId ?? generateId()) : pageRenderIdHeader; if (isSoftNavigation && !cookiePageRenderId && config.debug) { @@ -262,6 +291,7 @@ export async function handleEventPost( dispatchEvent, updateEvent, collectTemplates, + knownTemplateIds, }; const bodyType = body.type; diff --git a/packages/core/src/client.tsx b/packages/core/src/client.tsx index c6fd653..e5f1321 100644 --- a/packages/core/src/client.tsx +++ b/packages/core/src/client.tsx @@ -42,6 +42,8 @@ type NextlyticsContextValue = { addScripts: (scripts: TemplatizedScriptInsertion[]) => void; scriptsRef: React.MutableRefObject[]>; subscribersRef: React.MutableRefObject void>>; + mergeTemplates: (incoming?: Record) => void; + knownTemplateIdsRef: React.MutableRefObject; }; const NextlyticsContext = createContext(null); @@ -228,7 +230,11 @@ function NextlyticsScripts({ async function sendEventToServer( requestId: string, request: ClientRequest, - { signal, isSoftNavigation }: { signal?: AbortSignal; isSoftNavigation?: boolean } = {} + { + signal, + isSoftNavigation, + knownTemplateIds, + }: { signal?: AbortSignal; isSoftNavigation?: boolean; knownTemplateIds?: string[] } = {} ): Promise { try { const headers: Record = { @@ -238,6 +244,11 @@ async function sendEventToServer( if (isSoftNavigation) { headers[headerNames.isSoftNavigation] = "1"; } + // Tell the server which templates we already have so it only sends new ones. + // App Router clients already hold the full set, so nothing comes back. + if (knownTemplateIds?.length) { + headers[headerNames.knownTemplates] = knownTemplateIds.join(","); + } const response = await fetch("/api/event", { method: "POST", headers, @@ -294,6 +305,14 @@ export function NextlyticsClient(props: { ctx: NextlyticsContext; children?: Rea mergeTemplates(props.ctx.templates); }, [props.ctx.templates, mergeTemplates]); + // Mirror the template ids into a ref so sendEventToServer can tell the server + // which templates we already have (sent as a header), without re-creating the + // request callbacks on every merge. + const knownTemplateIdsRef = useRef(Object.keys(props.ctx.templates ?? {})); + useEffect(() => { + knownTemplateIdsRef.current = Object.keys(templates); + }, [templates]); + const addScripts = useCallback((newScripts: TemplatizedScriptInsertion[]) => { debug("Adding scripts", { newCount: newScripts.length, @@ -305,8 +324,16 @@ export function NextlyticsClient(props: { ctx: NextlyticsContext; children?: Rea // Context value is stable - refs don't change identity const contextValue = useMemo( - () => ({ requestId, templates, addScripts, scriptsRef, subscribersRef }), - [requestId, templates, addScripts] + () => ({ + requestId, + templates, + addScripts, + scriptsRef, + subscribersRef, + mergeTemplates, + knownTemplateIdsRef, + }), + [requestId, templates, addScripts, mergeTemplates] ); // Send page-view on mount and soft navigations @@ -316,7 +343,7 @@ export function NextlyticsClient(props: { ctx: NextlyticsContext; children?: Rea sendEventToServer( requestId, { type: "page-view", clientContext, softNavigation: softNavigation || undefined }, - { signal, isSoftNavigation: softNavigation } + { signal, isSoftNavigation: softNavigation, knownTemplateIds: knownTemplateIdsRef.current } ).then(({ items, templates: responseTemplates }) => { debug("page-view response", { scriptsCount: items?.length ?? 0 }); mergeTemplates(responseTemplates); @@ -349,28 +376,33 @@ export function useNextlytics(): NextlyticsClientApi { ); } - const { requestId, addScripts } = context; + const { requestId, addScripts, mergeTemplates, knownTemplateIdsRef } = context; const sendEvent = useCallback( async ( eventName: string, opts?: { props?: Record } ): Promise<{ ok: boolean }> => { - const result = await sendEventToServer(requestId, { - type: "custom-event", - name: eventName, - props: opts?.props, - collectedAt: new Date().toISOString(), - clientContext: createClientContext(), - }); + const result = await sendEventToServer( + requestId, + { + type: "custom-event", + name: eventName, + props: opts?.props, + collectedAt: new Date().toISOString(), + clientContext: createClientContext(), + }, + { knownTemplateIds: knownTemplateIdsRef.current } + ); + mergeTemplates(result.templates); if (result.items && result.items.length > 0) { addScripts(result.items); } return { ok: result.ok }; }, - [requestId, addScripts] + [requestId, addScripts, mergeTemplates, knownTemplateIdsRef] ); return { sendEvent }; diff --git a/packages/core/src/server-component-context.ts b/packages/core/src/server-component-context.ts index 8b8257a..f637c33 100644 --- a/packages/core/src/server-component-context.ts +++ b/packages/core/src/server-component-context.ts @@ -10,6 +10,9 @@ const headerNames = { isSoftNavigation: `${HEADER_PREFIX}is-soft-nav`, active: `${HEADER_PREFIX}active`, scripts: `${HEADER_PREFIX}scripts`, + /** Comma-separated template ids the client already holds, so the server only + * returns the ones it's missing (see api-handler / client templates merge). */ + knownTemplates: `${HEADER_PREFIX}known-templates`, } as const; export const LAST_PAGE_RENDER_ID_COOKIE = "last-page-render-id"; From 7e18284be4dca7469e446ac0261b28889ffef08f Mon Sep 17 00:00:00 2001 From: vklimontovich Date: Wed, 3 Jun 2026 18:34:03 -0400 Subject: [PATCH 3/4] test(e2e): cover Pages Router script delivery, simplify _app example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit getNextlyticsProps now tolerates a missing req, so the _app example no longer needs to guard ctx.req itself — call it directly, which doubles as the documented usage. Add an initial-load test (both routers) asserting the client-side script templates compile and run. On Pages Router this only works once templates arrive via /api/event, so it guards the fix; the prior script test was App-Router-only. Co-Authored-By: Claude Opus 4.8 (1M context) --- e2e/test-app/src/pages/_app.tsx | 11 ++++------- e2e/tests/analytics.test.ts | 35 +++++++++++++++++++++++++++++---- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/e2e/test-app/src/pages/_app.tsx b/e2e/test-app/src/pages/_app.tsx index f53ea50..4bc14a9 100644 --- a/e2e/test-app/src/pages/_app.tsx +++ b/e2e/test-app/src/pages/_app.tsx @@ -18,17 +18,14 @@ function MyApp({ Component, pageProps, nextlyticsCtx }: MyAppProps) { MyApp.getInitialProps = async (appContext: AppContext) => { const { ctx } = appContext; - // Only get nextlytics props on server-side (when req is available) - let nextlyticsCtx: NextlyticsContext = { requestId: "" }; - if (ctx.req) { - nextlyticsCtx = getNextlyticsProps({ req: { headers: ctx.req.headers } }); - } - + // getInitialProps re-runs in the browser on client-side navigation, where + // there is no `ctx.req`. getNextlyticsProps handles that and returns an empty + // context; the client keeps the templates and scripts it already has. return { pageProps: appContext.Component.getInitialProps ? await appContext.Component.getInitialProps(ctx) : {}, - nextlyticsCtx, + nextlyticsCtx: getNextlyticsProps(ctx), }; }; diff --git a/e2e/tests/analytics.test.ts b/e2e/tests/analytics.test.ts index b603b36..0e6352e 100644 --- a/e2e/tests/analytics.test.ts +++ b/e2e/tests/analytics.test.ts @@ -194,6 +194,33 @@ describe.each(versions)("%s", (version) => { await page.close(); }); + it("compiles and runs client-side script templates on initial load", async () => { + // Regression guard for template delivery. App Router gets templates from + // NextlyticsServer; Pages Router can't read config in getNextlyticsProps, + // so it receives them in the /api/event response. Either way the scripts + // must compile and run — without the template, __nextlyticsTestInit stays + // undefined and this times out. + const page = await testApp.newPage(); + + await testApp.visitHome(page); + + await page.waitForFunction(() => window.__nextlyticsTestInit !== undefined, undefined, { + timeout: 5000, + }); + + const counters = await page.evaluate(() => ({ + init: window.__nextlyticsTestInit, + config: window.__nextlyticsTestConfig, + event: window.__nextlyticsTestEvent, + })); + + expect(counters.init).toBeGreaterThanOrEqual(1); + expect(counters.config).toBeGreaterThanOrEqual(1); + expect(counters.event).toBeGreaterThanOrEqual(1); + + await page.close(); + }); + it("script modes work correctly during soft navigation", async () => { // This test only applies to App Router (soft navigation with ) if (routerType !== "app") return; @@ -264,11 +291,11 @@ describe.each(versions)("%s", (version) => { }); }); -// Type augmentation for test globals +// Type augmentation for test globals (set by the console-test backend scripts) declare global { interface Window { - __nextlyticsTestOnce?: number; - __nextlyticsTestParamsChange?: number; - __nextlyticsTestEveryRender?: number; + __nextlyticsTestInit?: number; + __nextlyticsTestConfig?: number; + __nextlyticsTestEvent?: number; } } From 654a643633ea375e621b3e41508a54d02fc23246 Mon Sep 17 00:00:00 2001 From: vklimontovich Date: Wed, 3 Jun 2026 21:13:05 -0400 Subject: [PATCH 4/4] style: apply prettier formatting Co-Authored-By: Claude Opus 4.8 --- packages/core/src/pages-router.ts | 5 ++++- packages/core/src/server.tsx | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/core/src/pages-router.ts b/packages/core/src/pages-router.ts index 7061536..b598fd5 100644 --- a/packages/core/src/pages-router.ts +++ b/packages/core/src/pages-router.ts @@ -2,7 +2,10 @@ import type { NextlyticsContext } from "./client"; import { restoreServerComponentContext } from "./server-component-context"; export type PagesRouterContext = { - req?: { headers: Record; cookies?: Record }; + req?: { + headers: Record; + cookies?: Record; + }; }; /** diff --git a/packages/core/src/server.tsx b/packages/core/src/server.tsx index 53625d5..5fe93c9 100644 --- a/packages/core/src/server.tsx +++ b/packages/core/src/server.tsx @@ -250,7 +250,9 @@ export function Nextlytics(userConfig: NextlyticsConfig): NextlyticsResult { if (!ctx) { // x-nl-page-render-id absent → check if middleware is at least active if (!headersList.get(headerNames.active)) { - console.warn("[Nextlytics] nextlyticsMiddleware should be added in order for Server to work"); + console.warn( + "[Nextlytics] nextlyticsMiddleware should be added in order for Server to work" + ); } return <>{children}; }