Next.js Discord

Discord Forum

next.js fetch is always fetching

Answered
Sage Thrasher posted this in #help-forum
Open in Discord
Sage ThrasherOP
import HomeHero from "@/layouts/home/HomeHero";
import ServerSection from "@/layouts/home/parts/ServerSections";
import { APIKey } from "@/lib/data";


export const revalidate = 300;

export default async function Page() {
  const response = await fetch(`${process.env.API_ENDPOINT}/guilds/home`, {
    headers: APIKey,
    next: {
      revalidate: 300
    }
  });

  const data = await response.json();


Every single time i refresh the page, this request is made and it's not being cached meaning my function invocations are increasing significantly on vercel. When running this on localhost it used cache but running it on vercel doesn't
Answered by Plague
You need to wrap any component that uses useSearchParams() in a Suspense boundary, otherwise it bubbles up the tree until it hits a Suspense boundary causing anything it bubbles through to be client-side rendered.
View full answer

133 Replies

Atlantic menhaden
bump
Sage ThrasherOP
anybody..?
maybe try removing one of the revalidate?
@Yi Lon Ma maybe try removing one of the revalidate?
Sage ThrasherOP
i originally didnt have the revalidate in the fetch request, i added that after to see if it fixed the issue which it didnt
can you show me the structure of APIKey
like not the key itself
the structure of that variable
@Yi Lon Ma can you show me the structure of `APIKey`
Sage ThrasherOP
ive now moved this fetch into a server action which works, however afaik this uses function invocations which is why i wanted to use the fetch directly in the server component because i dont believe cache hits increase function invocations

export const APIKey = {
    'backend-api-key': process.env.API_KEY,
    'Authorization': process.env.API_AUTHORIZATION
}
i also have other stuff in this same file if you want me to send that
this should ideally cache the data
can you create a minimal reproduction
Sage ThrasherOP
i also use import 'server-only' in the api key file
@Yi Lon Ma this should ideally cache the data
Sage ThrasherOP
it does locally, but not on vercel
@Yi Lon Ma can you create a minimal reproduction
Sage ThrasherOP
im not sure how to do this because i'd have to host it on vercel to see if its got the same issue.
do you have an existing nextjs app hosted on vercel you could add a fetch to, to see if it works for you?
The apps that I've hosted, caching works great in them
@Yi Lon Ma The apps that I've hosted, caching works great in them
Sage ThrasherOP
were you caching directly from the page file?
yes, adding revalidate in fetch api
Sage ThrasherOP
okay 2secs let me try and replicate it
@Yi Lon Ma yes, adding revalidate in fetch api
Sage ThrasherOP
thats weird, the cache works on the replicated version
i had other methods inside this function which i was reusing, maybe that caused the revalidation each time
also 2 things
1. does using server actions in server components count as a function invocation?
2. does using fetch in server components count as a function invocation if its to an external api?
@Yi Lon Ma I am not sure about the first one but seconds one counts as one
Sage ThrasherOP
what if its a cache hit?
i presume if the fetch gets from cache instead of making the request, its not a function invocation
Atlantic menhaden
bump
@Sage Thrasher also 2 things 1. does using server actions in server components count as a function invocation? 2. does using fetch in server components count as a function invocation if its to an external api?
Yes Server Actions count as a function invocation, a server action is an Serverless Function (Route Handler) under the hood
@Sage Thrasher also 2 things 1. does using server actions in server components count as a function invocation? 2. does using fetch in server components count as a function invocation if its to an external api?
fetch itself does not count as a function invocation as it isn't a Serverless or Edge function, the Serverless Function is the page that calls that fetch. It does contribute to the total execution time (compute) of the function though
@Plague `fetch` itself does not count as a function invocation as it isn't a Serverless or Edge function, the Serverless Function is the page that calls that fetch. It does contribute to the total execution time (compute) of the function though
Sage ThrasherOP
right now i use a lot of server actions for getting data and i mix it with react-query. the reason i use server actions for get is because its easier than route handlers, and making a fetch call to a route handler also increases function invocations.
as you can see, i've managed to reach 1.24mil function invocations, and im not quite sure how i could possible limit this as much as possible.
@Plague Does the fetching you are doing HAVE to be done from the client, does it depend on information only the client can know?
Sage ThrasherOP
well its quite far down the component tree im not quite sure how i could do it on the server
one of the fetches i perform on all pages doesnt require auth for the data because its not sensitive but its ran on the client because its far down the component tree
unless i could put a server component inside a client component and make a new component for it?
@Sage Thrasher one of the fetches i perform on all pages doesnt require auth for the data because its not sensitive but its ran on the client because its far down the component tree
You could instead make a Server Component wrapper around the client component fetch the data their wrap it in suspense and pass the data to the client, slight overhead but is worth it in my opinion.
Sage ThrasherOP
ive never used suspense before. this part of the data doesnt load in with the main page its only a "side bit" so would it mean i'd have to wait for this to fetch for everything else to render? also, how would i go about doing this, because this is on every single page so would i have to parse this down like 10 different components?
Page -> Header (client) -> Hero (client) -> Component where Fetch is
also, ive been having a lot of issues with next.js caching in the fetch method, one of my routes has 1 day revalidate (next: { revalidate: 86400 }) but the request is made everytime, and if i spam refresh my home page, a request is made to the api which caches data for an hour
Yeah, so you can still achieve what I stated by creating a wrapper around the hero that fetches the data, and passing that wrapper with the hero rendered inside to the header component through the children property see this [documentation](https://nextjs.org/docs/app/building-your-application/rendering/composition-patterns#supported-pattern-passing-server-components-to-client-components-as-props)
<Heading>
<HeroServerComponentWithClientComponentRenderedInside />
</Heading>
Sorry for the verbose name lmao but hopefully you understand what I meant
Sage ThrasherOP
but if the hero is a client component as well?
That's what I'm saying, you would render that Hero client component inside the HeroServerComponent which is the new wrapper you should make the fetch the data within
Then that HeroServerComponent would be passed to the Header component via children
Sage ThrasherOP
i could move header into its own separate component as its a fragment right now:
// Header.tsx
return (
  const [state, setState] = ...
  <>
    <Header state />
    <div>
      <Hero state setState />
    </div>
  </>
)
then render the hero into the server component
actually, maybe not because it uses a state
Yeah it won't work then, you cannot import a Server Component into a client component
With your current architecture I can see why you are fetching from the client, it's not a bad move and is sometimes necessary for certain use cases like this
Sage ThrasherOP
yeah, the issue is its on every single page so it can add up
and considering the fact that this route is the one where the fetch cache isnt working
every refresh a server action is called which fetches the api with 1day revalidation
Use a GET Route Handler instead, and cache the GET. Server Actions can't be cached but Route Handlers can
@Plague Use a GET Route Handler instead, and cache the GET. Server Actions can't be cached but Route Handlers can
Sage ThrasherOP
react-query does cache the server action when navigating between pages (but ofc a refresh gets rid of this cache)
but the GET fetch inside the server action isnt getting cached
and i dont know why
'use server';

import { APIKey } from "@/lib/data/APIKey";
import type { TagData } from "@/typing";

export async function fetchTrendingTags(
    { limit = Number.POSITIVE_INFINITY, offset = 0 }:
    { limit?: number, offset?: number } = {}
): Promise<TagData[]> {
    const url = new URL(`${process.env.API_ENDPOINT}/tags/trending`);
    if (limit) url.searchParams.set('limit', limit.toString());
    if (offset) url.searchParams.set('offset', offset.toString());

    const response = await fetch(url.toString(), {
        headers: APIKey,
        next: {
            revalidate: 86400
        }
    });
    if (!response.ok) return [];

    const data = await response.json();
    if (typeof data === 'undefined' || !Array.isArray(data)) return [];

    return data;
}
This is fetching to an external API outside of your Next application right?
What version of next are you using?
Sage ThrasherOP
├─┬ geist@1.3.1
│ └── next@14.2.15 deduped
├─┬ next-auth@5.0.0-beta.22
│ └── next@14.2.15 deduped
├─┬ next-client-cookies@1.1.1
│ └── next@14.2.15 deduped
├─┬ next-plausible@3.12.1
│ └── next@14.2.15 deduped
└── next@14.2.15
Next states in their documentation that fetch requests are not cached when used inside a Server Action or a POST request Route Handler, so you should change to a GET Route Handler if you want to cache that data
OR
use unstable_cache (not recommended honestly)
Sage ThrasherOP
alright so im probably better of using a route handler
Yes, however this won't reduce your function invocation issue
Sage ThrasherOP
however, i have the same thing with my home page where the data sometimes uses the cached but sometimes doesnt
export const revalidate = 3600;

export default async function Page() {
  const response = await fetch(`${process.env.API_ENDPOINT}/guilds/home`, {
    headers: APIKey,
    next: {
      revalidate: 3600
    }
  });

  const { featured, topVoted, trendingNew, recentlyVoted, randomGuilds, topTagGuilds } = await response.json();


and this is my home page fetch
it's probably because you are passing headers, which is request information so it needs to be dynamic to ensure it's there everytime.
Sage ThrasherOP
is there much i can do about this?
Honestly, only solution that I know of is unstable_cache. I use this heavily in my applications since I can't use the fetch API in my usecases
The API Key you are passing, is it static? How are you grabbing that value?
Also the syntax of your fetch seems off, unless the APIkey is an object your fetch should be something like
headers: {
Authorization/Bearer: APIKey
}
@Plague The API Key you are passing, is it static? How are you grabbing that value?
Sage ThrasherOP
yeah its static
import 'server-only';

export const APIKey = {
    'backend-api-key': process.env.API_KEY,
    'Authorization': process.env.API_AUTHORIZATION
}
Ah okay it is an object
Hmm if it's static, then the issue isn't the headers
@Plague Hmm if it's static, then the issue isn't the headers
Sage ThrasherOP
i switched to this and seems to work
'use server';

import { APIKey } from "@/lib/data/APIKey";
import { unstable_cache } from "next/cache";

export const getGuildsHome = unstable_cache(async () => {
    const response = await fetch(`${process.env.API_ENDPOINT}/guilds/home`, {
        headers: APIKey
    });
    return response.json();
}, ['guilds-home'], { revalidate: 3600 });
@Plague Next states in their documentation that `fetch` requests are not cached when used inside a Server Action or a POST request Route Handler, so you should change to a GET Route Handler if you want to cache that data
Sage ThrasherOP
i did read that nextjs doesnt cache fetch inside server actions, however when testing locally, it did work which is why i continued to use it so im not sure why this was
Yeah the Server Action I understand why it didn't work, but the page not fetch not working threw me off.

One thing to note, are you using that entire response? If not, I recommended stripping out the parts your application doesn't need otherwise you will be caching unnecessary amounts of data
Sounds good
Sage ThrasherOP
do you know another way i could make the client fetch server or am i pretty much stuck with that one because its inbetween 2 components needing the same state
"use client";

import { useState, type ReactNode } from "react";
import { useSearchQuery } from "@/hooks/useSearchQuery";
import { Header } from "@/components/layout/Header";
import { HeroPart } from "@/components/hero/HeroPart";

export default function HomeHero(props: { heroText?: string, children?: ReactNode } = {}) {
  const searchParams = useSearchQuery();
  const [showBg, setShowBg] = useState<boolean>(false);

  return (
    <>
      <Header bg={showBg} />
      <div className="flex flex-col">
        <HeroPart searchParams={searchParams} setIsSticky={setShowBg} heroTitle={props.heroText}>
          {props.children}
        </HeroPart>
      </div>
    </>
  );
}

the fetch is inside this HeroPart
this component is imported directly from the page.tsx
Wait this HomeHero component is imported directly into the page.tsx?
On every page?
Okay, but it only ever gets imported into the Page.tsx, that means you can just pass that data down only two components. That isn't that deep in the tree
Two components to drill through is nothing IMO.
Sage ThrasherOP
true, i thought it was a lot further, but thats probably on the other pages
Yeah try to find the page where it is the deepest
Sage ThrasherOP
so if theres only 1 element in this hero which needs a loader (the entire hero needs to be loaded because it has a search bar and page title), how could i make this only part have the loader?
Sage ThrasherOP
yeah, i've just moved it into the HeroPart
export function useSearchQuery(): [string, (inp: string, force?: boolean) => void, () => void] {
  const router = useRouter();
  const pathname = usePathname();
  const searchParams = useSearchParams();
  const query = searchParams.get('query');
  const [search, setSearch] = useState<string>(decode(query));

  useEffect(() => {
    setSearch(decode(query));
  }, [query]);

  const updateParams = (inp: string, commitToUrl = false) => {
    setSearch(inp);

    if (!commitToUrl) return;
    const newParams = new URLSearchParams(searchParams.toString());
    if (inp.length === 0) {
      newParams.delete('query');
    } else {
      newParams.set('query', inp);
    }

    if (!inp) {
      window.history.pushState(null, '', `${pathname}?${newParams.toString()}`);
    } else {
      router.replace(`/servers?${newParams.toString()}`);
    }
  };

  const onUnFocus = (newSearch?: string) => {
    updateParams(newSearch ?? search, true);
  };

  return [search, updateParams, onUnFocus];
}
lmao, that has been the issue this entire time
Your entire page.tsx is being client-side rendered right now
that's why it's always fresh
You need to wrap any component that uses useSearchParams() in a Suspense boundary, otherwise it bubbles up the tree until it hits a Suspense boundary causing anything it bubbles through to be client-side rendered.
Answer
Sage ThrasherOP
should i remove it entirely and get searchParams from Page
@Sage Thrasher should i remove it entirely and get searchParams from Page
Unneccessary if the page doesn't require the params, cause it'l make your page dynamically rendered, eseentially causing the same thing.
Just add Suspense
Sage ThrasherOP
around HomeHero?
or around HeroPart now that i moved it into that
Yup
Whatever calls useSearchQuery/Params
Sage ThrasherOP
if my page.tsx was being rendered on the client, wouldn't i see that api call in the network tab though?
Always need to understand the APIs you are using inside of Next.js, almost all of them affect the rendering model in some way shape or form
Sage ThrasherOP
ah
also, for that data which needs fetching inside the HeroPart, how would i add a loader for it if im fetching it on the server? or would it not matter if its cached because of how quickly it would be retrieved
Wrap it in Suspense
Suspense is your friend in Next.js/React for all things async
Probably my favorite react API ever.
Sage ThrasherOP
how does suspense actually work? would it still render instantly or? because i do want the hero to render, just 1 part of it should display a loader
Suspense shows a fallback (if you specify one, which you should IMO) until the component resolves then it shows the component. Component resolves after it's async operations have finished (aka the await)
Sage ThrasherOP
ah okay so i could remake the component using the loader and once loaded, render the one without the loader
Yup, that is exactly what I do with Suspense I create the same HTML structure without the async operations and use that as the fallback shell then replace it with the real component afterwards.
to the user it almost looks like nothing even happened (besides the search bar being disabled, muted, and slightly pulsing so they know it isn't ready to be used yet)
@Plague Yup, that is exactly what I do with `Suspense` I create the same HTML structure without the async operations and use that as the fallback shell then replace it with the real component afterwards.
Sage ThrasherOP
so would i be doing something like this:
export async function Page() {
  const tags = await fetchTags(); // will be fetch request

  return (
    <Suspense fallback={<HeroPartLoader/>}
      <HeroPart>
        <div>
          {tags.map....}
        </div>
      </HeroPart>
    </Suspense>
  )
}


or something like this
export async function Page() {
  const tags = await fetchTags(); // will be fetch request

  return (
    <Suspense fallback={<HeroPartLoader/>}
      <HeroPart tags={tags} />
    </Suspense>
  )
}
Yeah either works, second is cleaner. Be sure to also have a loading.tsx/jsx file wrapping that page component as well since it fetches data, so you can display a instant loading UI for users
@Plague Yeah either works, second is cleaner. Be sure to also have a `loading.tsx/jsx` file wrapping that page component as well since it fetches data, so you can display a instant loading UI for users
Sage ThrasherOP
is there a way i could do the loading.tsx file on / without setting it for all routes? i suppose not right
There is a way, admittedly annoying but there is a way using Route Groups. Open a new forum post if you have questions about that so that this one doesn't get cluttered with unrelated questions/responses
I also do this in my application.
@Plague Remember to mark this as solution for this thread.
Sage ThrasherOP
You don't mind if i come back here for follow up questions, do you?
@Sage Thrasher You don't mind if i come back here for follow up questions, do you?
I don't, but, you should really open up new forum posts for that. You can tag me in them though if you'd like cause we can always view this one for context.
Sage ThrasherOP
ahh wait, i misunderstood, you put the fetch inside a component, then that component goes inside the suspense and returns the target component
Yup you got it