Skip to content

Nitro applies immutable cache headers to non-static responses for missing assets, causing CDN cache poisoning #7215

@yergom

Description

@yergom

Which project does this relate to?

Start

Describe the bug

When using TanStack Start with Nitro, requests to non-existent files under /assets/* are handled by the TanStack Start entry point (which may render the app shell, an error route, or any other response). Nitro then applies Cache-Control: public, max-age=31536000, immutable to that response because it matches the /assets/* path and returns a 200 status.

This is particularly dangerous in zero-downtime deployment scenarios where old and new server versions overlap briefly behind a load balancer:

  1. A browser fetches index.html (never cached) and gets the new version, which references index-<newHash>.js.
  2. The CDN doesn't have index-<newHash>.js yet, so it forwards the request to origin.
  3. The load balancer may route that request to the old server, which doesn't have that file.
  4. The old server handles the request through the TanStack Start entry point, returning a 200 and the shell. Nitro applies immutable cache headers to it.
  5. The CDN permanently caches the wrong response at that edge node. All users in that region get a broken app that can't self-heal.

This is not a theoretical problem — we've experienced it in production.

Current workaround

We created a catch-all TanStack Start route at /assets/$ that explicitly returns a 404:

import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/assets/$")({
  component: () => <div>Not found</div>,
  server: {
    handlers: {
      ANY: () => {
        return new Response("not found", { status: 404 });
      },
    },
  },
});

This works because Nitro serves real static files before TanStack Start handlers run, and Nitro only applies immutable cache headers to 200 responses.

Your Example Website or App

--

Steps to Reproduce the Bug or Issue

--

Expected behavior

I would expect to either:

  • Requests to non-existent assets under /assets/* should return a 404, not be handled by the TanStack Start entry point. Nitro should not apply immutable cache headers to non-static responses.

OR

  • Cache headers are only applied to assets built by vite. Non existent asset files can be routed to tanstack start, but they should not have the headers overwritten.

Screenshots or Videos

No response

Platform

Additional context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions