Skip to content

Instantly share code, notes, and snippets.

@itsMapleLeaf
Created October 27, 2022 17:10
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save itsMapleLeaf/6875c6816329879ecc7326b60a5929d4 to your computer and use it in GitHub Desktop.
Save itsMapleLeaf/6875c6816329879ecc7326b60a5929d4 to your computer and use it in GitHub Desktop.
thoughts on Next.js fetch

I think it's fair to expect divergence with an API that was made for browsers being mimicked elsewhere; here it's more about faithfulness and predictability

node-fetch, cross-fetch, undici fetch, deno fetch, etc. these are all faithful to the original API, and behave in ways that wouldn't surprise you if you're familiar with fetch. the more unfamiliar nooks and crannies of the API (e.g. ReadableStream) is sort of a rats nest unfortunately 😅 but that's beside the point

here's the difference with Next.js: now I'm not writing fetch with fetch in mind, I'm writing fetch with Next.js's caching behaviors in mind, and a hidden fetch call multiple levels down in the stack can unexpectedly change that behavior in potentially unexpected ways

happy to be convinced otherwise though. I know y'all give this a lot of thought, but this was just really out of left field for me 🫠

@sebmarkbage
Copy link

sebmarkbage commented Oct 27, 2022

It's important that it's the same fetch() api so that you can actually use existing libraries from anywhere. It can't be a custom one or it would defeat the purpose. There would be too many libraries that call fetch() and cause mistakes. There would be no point. If anything the alternative would be to error for any library that uses plain fetch and redirect users to something that doesn't.

Note that there are a lot of Node.js libraries that already add deduping because it's such a common problem already. It's easy to make the mistake that you fetch it again. One alternative would be to just recommend everyone use one of those but what's the point then?

There are a lot of bailout cases, including mutative verbs. Really, for almost anything you should be implementing an AbortSignal to fetch. If you do, then it doesn't kick in and respects your own life time.

For almost all scenarios the dedupe behavior is something browsers and other environments already implement. E.g. a browser will not issue a duplicate fetch request until the first response comes back - and only if that response comes back specifically with a Cache-control: no-store header will it even issue the second request. The issue is that those cache headers are meant to communicate that it shouldn't store it long lived like on disk. It doesn't actually understand the semantic domain specific knowledge of the request life time of an SSR render. So it's only in that specific scenario that it needs to cache it in memory - a little longer - but only for the lifetime of that SSR render and not shared across incoming requests.

I'd consider taking a look at what scenario that you're concerned about or trying to find bugs in real code and report back.

@tom-sherman
Copy link

tom-sherman commented Oct 27, 2022

Thanks for helping me get my head around this, it's really helpful - was very skeptical before this comment but now I'm open to seeing how it all plays out.

The important bit for me was the idea of transparently extending fetch for the duration of a single server render.

I'm deep into algebraic effects right now, so in my mind what you're doing sounds like implementing a "fetch effect" in the React runtime. It doesn't leak outside of the render() call at all, and any fetch() calls inside of the component tree (but not outside) will follow the caching semantics. Opting out with an AbortSignal is cool too, I guess in a typed alg effect lang that's kind of equivalent to producing your own distinct fetch effect to indicate that you want to handle the lifetime of the request yourself.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment