From 9df383670d1fb4b4a7ec5ee7b6d01cf05df9e9e5 Mon Sep 17 00:00:00 2001 From: Rafa Audibert Date: Wed, 11 Dec 2024 18:24:19 -0300 Subject: [PATCH 1/2] feat: Allow users to customize `fetch` behavior NextJS SSR v15.1+ will not cache `fetch` requests by default, but some users might want that, specially in development mode. We're now allowing them to customize our `fetch` requests by setting the new `fetch_options` setting when calling `posthog.init` This needs to be used carefully, but it should be extremely powerful and useful for NextJS users. We'll add documentation about it on `posthog.com` soon. --- src/posthog-core.ts | 4 ++++ src/request.ts | 1 + src/types.ts | 16 ++++++++++++++++ 3 files changed, 21 insertions(+) diff --git a/src/posthog-core.ts b/src/posthog-core.ts index a310f84e5..3fb2790e2 100644 --- a/src/posthog-core.ts +++ b/src/posthog-core.ts @@ -663,6 +663,10 @@ export class PostHog { } options.compression = options.compression === 'best-available' ? this.compression : options.compression + // Specially useful if you're doing SSR with NextJS + // Users must be careful when tweaking `cache` because they might get out-of-date feature flags + options.fetchOptions = options.fetchOptions || this.config.fetch_options + request({ ...options, callback: (response) => { diff --git a/src/request.ts b/src/request.ts index 3d5a46bb7..ea4ab1343 100644 --- a/src/request.ts +++ b/src/request.ts @@ -165,6 +165,7 @@ const _fetch = (options: RequestOptions) => { keepalive: options.method === 'POST' && (estimatedSize || 0) < KEEP_ALIVE_THRESHOLD, body, signal: aborter?.signal, + ...options.fetchOptions, }) .then((response) => { return response.text().then((responseText) => { diff --git a/src/types.ts b/src/types.ts index 303b501b1..3431b4cde 100644 --- a/src/types.ts +++ b/src/types.ts @@ -333,6 +333,15 @@ export interface PostHogConfig { events_burst_limit?: number } + /** Used when sending data via `fetch`, use with care, this is intentionally meant to be used with NextJS `fetch` + * Incorrect usage may cause out-of-date data for feature flags, actions tracking, etc. + * See https://nextjs.org/docs/app/api-reference/functions/fetch#fetchurl-options + */ + fetch_options?: { + cache?: RequestInit['cache'] + next_options?: NextOptions + } + /** * PREVIEW - MAY CHANGE WITHOUT WARNING - DO NOT USE IN PRODUCTION * whether to wrap fetch and add tracing headers to the request @@ -440,6 +449,9 @@ export interface RequestResponse { export type RequestCallback = (response: RequestResponse) => void +// See https://nextjs.org/docs/app/api-reference/functions/fetch#fetchurl-options +type NextOptions = { revalidate: false | 0 | number; tags: string[] } + export interface RequestOptions { url: string // Data can be a single object or an array of objects when batched @@ -452,6 +464,10 @@ export interface RequestOptions { timeout?: number noRetries?: boolean compression?: Compression | 'best-available' + fetchOptions?: { + cache?: RequestInit['cache'] + next?: NextOptions + } } // Queued request types - the same as a request but with additional queueing information From f088b432716f4699d1d02359a191fbf9f6380066 Mon Sep 17 00:00:00 2001 From: Rafa Audibert Date: Wed, 11 Dec 2024 18:50:59 -0300 Subject: [PATCH 2/2] test: Add unit tests for new fetch behavior --- src/__tests__/request.test.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/__tests__/request.test.ts b/src/__tests__/request.test.ts index 6456321d0..b098a1d5f 100644 --- a/src/__tests__/request.test.ts +++ b/src/__tests__/request.test.ts @@ -186,6 +186,23 @@ describe('request', () => { }) }) + it('supports nextOptions parameter', async () => { + request( + createRequest({ + fetchOptions: { cache: 'force-cache', next: { revalidate: 0, tags: ['test'] } }, + }) + ) + await flushPromises() + + expect(mockedFetch).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + cache: 'force-cache', + next: { revalidate: 0, tags: ['test'] }, + }) + ) + }) + describe('keepalive with fetch and large bodies can cause some browsers to reject network calls', () => { it.each([ ['always keepalive with small json POST', 'POST', 'small', undefined, true, ''],