nitro icon indicating copy to clipboard operation
nitro copied to clipboard

`publicAssets` with `maxAge` not working in some cases

Open hi-ogawa opened this issue 3 months ago • 3 comments

Environment

stackblitz with "nitro": "npm:nitro-nightly@latest"

Reproduction

https://stackblitz.com/edit/github-83dbpmnp?file=nitro.config.ts

Describe the bug

With the following config nitro config with publicAssets and maxAge:

export default defineNitroConfig({
  srcDir: 'server',
  publicAssets: [
    // maxAge at root doesn't work for any presets?
    {
      dir: './client/',
      baseURL: '/',
      maxAge: 10,
    },
    // maxAge anywhere doesn't work for node-server preset?
    {
      dir: './client/nested/',
      baseURL: '/nested/',
      maxAge: 20,
    },
  ],
});

and building with node-server preset,

NITRO_PRESET=node-server nitro build
node .output/server/index.mjs

neither of asset response includes cache-control.

curl -v http://127.0.0.1:3000/asset1.txt
curl -v http://127.0.0.1:3000/nested/asset2.txt

Building with vercel preset NITRO_PRESET=vercel nitro build, it generates a following .vercel/output/config.json, which I'm not sure where max-age=31536000 is coming from and whether this is working intended.

{
  "version": 3,
  "overrides": {},
  "routes": [
    {
      "headers": {
        "cache-control": "public, max-age=20, immutable"
      },
      "src": "/nested/(.*)"
    },
    {
      "src": "/nested(.*)",
      "headers": {
        "cache-control": "public,max-age=31536000,immutable"
      },
      "continue": true
    },
    {
      "handle": "filesystem"
    },
    {
      "src": "/(.*)",
      "dest": "/__fallback"
    }
  ]
}

Additional context

One question: I noticed Nitro has routeRules which allows custom headers. I haven't tried, but is routeRoles preferred over nesting publicAssets like above?

Personally root assets not having cache-control seems fine, but being able to add cache-control only for some directory inside should work (at least either via publicAssets or routeRules).

I think this is the same issue I'm seeing in https://github.com/nitrojs/nitro/pull/3586 with the default node-server preset.

Logs


hi-ogawa avatar Sep 24 '25 10:09 hi-ogawa

Thanks for the issue. In fact Nitro extends route rules (as single source of trust) from public assets (src).

Note: Base URL of / is considered a "fall though" (because it can overlap with other routes therefore we don't have long term caching for those assets unless clearly prefixed with something like /client or /_nuxt, etc)

There is a chance we regressed this behavior in recent v3 changes. I will investigate more.

pi0 avatar Sep 24 '25 11:09 pi0

Found regression, after compiled routing (#3575) route rules were being applied after global middleware (serve static was one of them) therefore missing additional headers. https://github.com/nitrojs/nitro/commit/9fe8cd4655b0b53c07d77b0a56acb8d5f330d529 fixes is.

pi0 avatar Sep 24 '25 11:09 pi0

Thanks for the issue. In fact Nitro extends route rules (as single source of trust) from public assets (src).

Note: Base URL of / is considered a "fall though" (because it can overlap with other routes therefore we don't have long term caching for those assets unless clearly prefixed with something like /client or /_nuxt, etc)

Thanks for the pointer. That's good to know 🙏 Yes, the root fall through makes sense and my repro above with maxAge was for artificial purpose. I just wanted to confirm the behavior.


For vercel routes, it generated two more routes with max-age=20 and max-age=31536000. Is this expected?

hi-ogawa avatar Sep 25 '25 02:09 hi-ogawa