Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Always invalidating cache using fetch in NextJs #1529

Closed
maiconsanson opened this issue Nov 17, 2024 · 8 comments
Closed

Always invalidating cache using fetch in NextJs #1529

maiconsanson opened this issue Nov 17, 2024 · 8 comments
Assignees
Labels
SDK Support General SDK issues rather than being related to a feature team

Comments

@maiconsanson
Copy link

maiconsanson commented Nov 17, 2024

Bug Description

After enabling fetch logs in the next.config, I noticed that the cached result from API calls using the extended native fetch is always invalidated with the message: Cache skipped reason: (cache-control: no-cache (hard refresh)) when fetching any API endpoint from a page.

I'm using "next": "15.0.3" with the app folder and followed the PostHog documentation on using the client.

The result is always fresh when hitting the same endpoint multiple times:
Screenshot 2024-11-17 at 11 09 43

However, it should be the cached result:
Screenshot 2024-11-17 at 12 02 24

How to reproduce

  1. Enable the fetch logs in the next.config.
const nextConfig = {
  logging: {
    fetches: {
      fullUrl: true,
    },
  },
}
  1. Follow the app router docs (client version).
  2. Make a request from a page to an API endpoint using the native fetch (setting a cache time).

Additional context

  1. With the server-side configuration using the posthog-node package, the problem doesn't occur, but it's not possible to use session replays.
  2. The pages are server-side rendered, and I'm using the client provider to wrap them. Could that be the problem?
  3. My layout is a server component, so I can't import the PostHogPageView using dynamic with ssr: false as stated in the documentation:
// Layout: without dynamic importing of PostHogPageView

import { PostHogClientProvider } from '@src/app/providers'

// irrelevant code

 <PostHogClientProvider isProduction={isProduction}>
  <Suspense fallback={null}>
    <PostHogPageView />
  </Suspense>
  {children}
</PostHogClientProvider>
  1. Even when importing the PostHogPageView dynamically inside the provider, the behavior remains the same:
// Provider: with dynamic importing of PostHogPageView

'use client'

import { useEffect } from 'react'
import dynamic from 'next/dynamic'
import posthog from 'posthog-js'
import { PostHogProvider } from 'posthog-js/react'
const PostHogPageView = dynamic(() => import('@components/PostHogPageView'), {
  ssr: false,
})

const POSTHOG_KEY = process.env.NEXT_PUBLIC_POSTHOG_KEY
const POSTHOG_HOST = process.env.NEXT_PUBLIC_POSTHOG_HOST

export default function PostHogClientProvider({ children, isProduction }) {
  useEffect(() => {
    if (isProduction) {
      posthog.init(POSTHOG_KEY, {
        api_host: POSTHOG_HOST,
        person_profiles: 'identified_only', 
        capture_pageview: false,
        capture_pageleave: true,
      })
    }
  }, [])

  return isProduction ? (
    <PostHogProvider client={posthog}>
      <PostHogPageView />
      {children}
    </PostHogProvider>
  ) : (
    children
  )
}
@robbie-c robbie-c added the SDK Support General SDK issues rather than being related to a feature team label Nov 29, 2024
@rafaeelaudibert rafaeelaudibert self-assigned this Dec 11, 2024
@rafaeelaudibert
Copy link
Member

Hey, @maiconsanson!

I'd love for you to clarify something: you mention the /api endpoint is failing, but you then show how you're configuring Posthog in the frontend.

The fact that your /api request is failing seems to indicate you're using posthog in the backend as well, is that correct? Note that from NextJS 15.1 onwards all the fetch requests will not be cached by default, so this could be happening because your own fetch requests are not properly set in the backend. It is true, however, that posthog-js doesn't make much effort to help with that, but I'd like to confirm if you're having this problem in the /api backend or in the server-rendered client pages.

@rafaeelaudibert
Copy link
Member

rafaeelaudibert commented Dec 12, 2024

You should be able to customize posthog-js's fetch behaviour on posthog-js v1.198.0+: https://github.com/PostHog/posthog-js/releases/tag/v1.198.0

I'll be adding documentation by EOD

@maiconsanson
Copy link
Author

@rafaeelaudibert

Thank you for your response!

The issue arises when a server-rendered page invokes any API endpoint using fetch while setting a cache time.

The PostHog configuration is in the frontend, but the fetch call, which always skips the configured cache time, is made from the server-rendered page.

This behavior only occurs when I initialize the PostHog client provider inside a useEffect. It seems this setup triggers a hard refresh when navigating to a new page, causing the previously cached responses to be discarded. In my app, I consistently encounter cache misses, regardless of the cache time set for fetch when using Posthog.

I tested the latest version with the new options you introduced, but the problem persists.

If helpful, I could create a full example in a new repository to further investigate and clarify the issue.

@rafaeelaudibert
Copy link
Member

Hey!

I'd love if you could provide me with a full repro repo. I've tried reproducing it myself, and once I set the new advanced fetch configuration (available from v1.198.0 onwards) I see the data being correctly cached.

ou're saying you're using the API, so I wouldn't expect you to be using the posthog configuration that's coming from inside a useEffect, you should be initializing it separately at the API level. Maybe that's the problem here?

@maiconsanson
Copy link
Author

@rafaeelaudibert I’ve identified the root cause of this issue. My application includes the Cookie header in all outgoing fetch requests. The presence of PostHog seems to interfere with the caching mechanism, potentially because of how cookies are handled in combination with fetch.

By removing the cookies from the fetch, it worked.

This might help others facing similar issues!

const cookieStore = await cookies()

const fetchWithOptions = async ({ body, cacheTime, method, tags, url }) => {
  const options = {
    method,
    credentials: 'include',
    headers: {
      Cookie: cookieStore?.toString() || '', // root of the problem
      [X_INTERNAL_API]: apiSecret || headersList?.get(X_INTERNAL_API) || '',
    },
  }

  if (isPresent(body) && method !== 'GET') {
    options.body = JSON.stringify(body)
  }

  if (isPresent(cacheTime)) {
    options.next = { revalidate: cacheTime, tags }
  }

  const response = await fetch(url, options)
  const jsonResponse = await response.json()

  return jsonResponse
}

@rafaeelaudibert
Copy link
Member

@maiconsanson Hmm, that's interesting. I wouldn't expect that to be happening. If you are'nt using our cookies in your server, you could also attempt to store the user data on localstorage rather than the default localstorage+cookies - this can be set when initializing posthog-js.

Did you stop sending cookies at all or simply removed our entry?

@maiconsanson
Copy link
Author

@maiconsanson Hmm, that's interesting. I wouldn't expect that to be happening. If you are'nt using our cookies in your server, you could also attempt to store the user data on localstorage rather than the default localstorage+cookies - this can be set when initializing posthog-js.

Did you stop sending cookies at all or simply removed our entry?

The PostHog initialization is currently disabled and I'm figuring out the best way to handle the issue now that I understand what's happening.

I tested using the persistence: 'localStorage', and it worked perfectly!
Thank you for the helpful tip!

@maiconsanson
Copy link
Author

@rafaeelaudibert

I think the issue is now clear. I was sending all cookies (including PostHog's) with each fetch request, which caused the side effect:

Cookie: cookieStore?.toString()

Restricting the request to only send the necessary cookies resolved the issue.

I’ll close this issue now.

Thank you for your support!

@rafaeelaudibert rafaeelaudibert closed this as not planned Won't fix, can't repro, duplicate, stale Jan 8, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
SDK Support General SDK issues rather than being related to a feature team
Projects
None yet
Development

No branches or pull requests

3 participants