Skip to content

feat(tanstack): /_serverFn CDN caching arrives with a version bump (opt-out) - #523

Merged
JonasJesus42 merged 1 commit into
mainfrom
feat/serverfn-cdn-by-default
Sep 1, 2026
Merged

feat(tanstack): /_serverFn CDN caching arrives with a version bump (opt-out)#523
JonasJesus42 merged 1 commit into
mainfrom
feat/serverfn-cdn-by-default

Conversation

@JonasJesus42

@JonasJesus42 JonasJesus42 commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #513. That PR shipped the feature but left it needing two edits in every site — a
src/start.ts and a cdnCacheControl option. That's the same per-site boilerplate the package split exists
to remove: twenty sites, twenty PRs, and any site that skips one silently gets nothing.

This makes it opt-out: a site picks the feature up from a version bump, and only touches config to turn it
off.

1. The default flips to "serverfn-segment"

Safe because the mode is inert until a verified marker arrives. A client that never sends __cseg keeps
getting no-store, so flipping the default cannot change behaviour on its own.

Three tests pin that claim instead of leaving it in a comment:

  • no marker (HTML and /_serverFn) → stays no-store
  • valid marker → engages, with zero site config
  • cdnCacheControl: "no-store" → still opts out

2. The vite plugin supplies a default Start entry

This is the part that actually unlocks "just bump".

#tanstack-start-entry is a subpath import of @tanstack/start-client-core. With no src/start.ts it
resolves to a fake entry exporting startInstance = undefined, so the site gets no serverFns.fetch hook
— and that hook is what appends the marker to the URL. Without it, the worker-side default above would never
see a marker and the feature could never engage.

decoVitePlugin now aliases that specifier to sdk/startEntry when the site has no start file of its own.

Strictly conditional on absence. A site that owns its src/start.ts keeps it — ours would otherwise
silently replace whatever else it configures there. Such a site opts in by composing decoServerFnFetch
itself, exactly as before.

Verified end to end, not just by unit test

Took a real site, removed the site-level config (src/start.ts deleted, cdnCacheControl removed), built
it, ran it under wrangler dev, and drove SPA navigation with Chrome:

GET /_serverFn/…?__cseg=desktop.<build>   →   CDN-Cache-Control: public, max-age=1800

Fail-closed intact on that same zero-config build:

case result
correct marker public, max-age=1800
no marker no-store
wrong device (forged) no-store
bot UA no-store
login cookie no-store
HTML document no-store

2590 tests passing, +3 new. Typecheck clean across the three packages.

The 4 draft preview failures are pre-existing on main.

What this means for the sites

Sites with a pending version bump (montecarlo #202, zeedog #168 and others) get CDN caching of SPA navigation
and prefetch with no further change. buildSegment is still required for the marker to verify — sites without
one keep no-store and get the boot warning added in #512.

HTML is still no-store, and still needs separate work.

🤖 Generated with Claude Code


Summary by cubic

Makes CDN caching of /_serverFn arrive with a version bump instead of a per-site PR. The cdnCacheControl default flips from "no-store" to "serverfn-segment", and decoVitePlugin now supplies a default Start entry when the site has no src/start.ts of its own.

  • The new default is inert until a verified __cseg marker arrives, so clients without one still get no-store.
  • The default Start entry wires decoServerFnFetch, the hook that appends the marker to /_serverFn URLs.
  • Sites with their own src/start.ts keep it; an explicit cdnCacheControl: "no-store" still opts out.
  • HTML documents stay no-store.

Written for commit 5d7cf60. Summary will update on new commits.

Review in cubic

Shipping the previous PR left the feature needing two edits in every site — a
`src/start.ts` and a `cdnCacheControl` option. That is the same per-site
boilerplate the package split exists to remove: twenty sites, twenty PRs, and
any site that skips one silently gets nothing.

Two changes make it opt-OUT instead.

## The default flips to "serverfn-segment"

Safe because the mode is inert until a verified marker arrives: a client that
never sends `__cseg` keeps getting `no-store`, so enabling it cannot change
behaviour on its own. Three tests pin that claim rather than leaving it in a
comment — no marker (HTML and `/_serverFn`) stays `no-store`, a valid marker
engages with no site config, and `cdnCacheControl: "no-store"` still opts out.

## The vite plugin supplies a default Start entry

`#tanstack-start-entry` is a subpath import of `@tanstack/start-client-core`.
With no `src/start.ts` it resolves to a fake entry exporting
`startInstance = undefined`, so the site gets no `serverFns.fetch` hook — and
that hook is what appends the marker. Without it the worker-side default above
would never see a marker and the feature could not engage.

`decoVitePlugin` now aliases that specifier to `sdk/startEntry` when the site
has no start file of its own. Strictly conditional on absence: a site that owns
its `src/start.ts` keeps it, since ours would otherwise silently replace
whatever else it configures there.

## Verified end to end, not just by unit test

Built a real site with the site-level config REMOVED (no `src/start.ts`, no
`cdnCacheControl`), ran it under `wrangler dev`, and drove SPA navigation with
Chrome:

    GET /_serverFn/…  __cseg=desktop.<build>  →  CDN-Cache-Control: public, max-age=1800

and fail-closed intact on the same build: no marker, wrong device, bot UA and
login cookie all still `no-store`, HTML documents still `no-store`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@JonasJesus42
JonasJesus42 requested a review from a team September 1, 2026 15:12
@JonasJesus42
JonasJesus42 merged commit bc6b2e2 into main Sep 1, 2026
1 check passed
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 7.57.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant