From 2b0374694d918afd039d07dc11aa8f228ecbe24f Mon Sep 17 00:00:00 2001 From: vklimontovich Date: Sat, 6 Jun 2026 21:33:03 -0400 Subject: [PATCH] fix(core): count only GET (not HEAD) as a pageView on non-API routes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A HEAD is a metadata probe (uptime monitors, link checkers, cache revalidation) — nothing is viewed — so it shouldn't be a pageView. Track only GET and document navigations; HEAD now joins POST/PUT/etc. in being skipped on non-API routes. API paths are unaffected (still apiCall for any method). --- e2e/tests/analytics.test.ts | 11 ++++++----- packages/core/src/middleware.ts | 13 ++++++------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/e2e/tests/analytics.test.ts b/e2e/tests/analytics.test.ts index de23c20..16c791d 100644 --- a/e2e/tests/analytics.test.ts +++ b/e2e/tests/analytics.test.ts @@ -159,12 +159,13 @@ describe.each(versions)("%s", (version) => { expect(matches.length).toBe(0); }); - it("does not track non-GET requests to non-API routes (e.g. webhook POST)", async () => { - // A POST/PUT/etc. to a non-API route (a webhook or programmatic write to a - // Route Handler) is not a page view. The middleware runs regardless of - // whether the handler accepts POST, so this asserts the request is skipped - // even though /raw-data only implements GET. + it("does not track non-GET requests to non-API routes (POST, HEAD)", async () => { + // Only GET (and document navigations) are page views on non-API routes. A + // POST/PUT/etc. is a write; a HEAD is a metadata probe — neither is a view. + // The middleware runs regardless of whether the handler implements these + // methods (/raw-data only implements GET). await fetch(`${testApp.baseUrl}/raw-data`, { method: "POST" }); + await fetch(`${testApp.baseUrl}/raw-data`, { method: "HEAD" }); await new Promise((r) => setTimeout(r, 500)); const events = await testApp.getAnalyticsEvents(); diff --git a/packages/core/src/middleware.ts b/packages/core/src/middleware.ts index 1b15ed8..ce412e1 100644 --- a/packages/core/src/middleware.ts +++ b/packages/core/src/middleware.ts @@ -107,13 +107,12 @@ export function createNextlyticsMiddleware( return response; } - // On non-API routes, only reads (GET/HEAD) and document navigations are - // pageViews. A non-read, non-navigation request to a non-API route — e.g. a - // webhook or programmatic POST/PUT to a Route Handler — is not a page view, - // so skip it. (A classic server-rendered form POST is a document navigation, - // so it's kept.) API paths flow through for any method → apiCall. - const isReadMethod = request.method === "GET" || request.method === "HEAD"; - if (!isReadMethod && !reqInfo.isDocumentRequest && !config.isApiPath(pathname)) { + // On non-API routes, only GET and document navigations are pageViews. A HEAD + // is a metadata probe (monitors, link checkers, cache revalidation) — nothing + // is viewed — and POST/PUT/etc. are writes; none are page views, so skip them. + // (A classic server-rendered form POST is a document navigation, so it's + // kept.) API paths flow through for any method → apiCall. + if (request.method !== "GET" && !reqInfo.isDocumentRequest && !config.isApiPath(pathname)) { const response = NextResponse.next(); response.headers.set(headerNames.active, "1"); return response;