Skip to content

Island JS chunks not being cached (Cache-Control: no-cache) when using @fresh/plugin-vite #3784

@andreluizsgf

Description

@andreluizsgf

Hi! I'm running into an issue with island caching behavior that seems different of the expected behavior.

From my understanding, Fresh should cache island JS bundles aggressively (immutable, long max-age) when served from paths like:

/_fresh/js/{BUILD_ID}/chunk-XXXX.js

However, that's not what I'm observing in my app.

What I'm seeing:

  • Every page refresh triggers new HTTP requests for all islands
  • The number of server requests remains the same between first and subsequent visits
  • Each island request includes:
    • Cache-Control: no-cache
    • A new X-Deno-Trace-Id on every refresh
    • Image
  • This suggests that nothing is being served from browser cache

In traces, each island appears as a separate request on every reload.
Image

Expected behavior:

  • First visit → island chunks fetched and cached
  • Subsequent visits → served from browser cache (no network requests)
  • After deploy → only changed chunks fetched

Environment:

@fresh/plugin-vite@1.0.8
vite@7.2.7
Using Vite builder (not legacy)

Important detail:

From discussion, it was mentioned that Fresh only applies caching to assets served from:

/_fresh/js/{BUILD_ID}/...

or assets suffixed with:

?__frsh_c=BUILD_ID

In my case, the island assets seem to be served from a different path, assets/fresh-island..., which might explain why caching headers are not applied.

Below there is my vite.config.ts

vite.config.ts
import { defineConfig } from 'vite';
import { fresh } from '@fresh/plugin-vite';
import tailwindcss from '@tailwindcss/vite';
import { initializeRoutes } from '@utils/router.ts';

export default defineConfig({
  plugins: [
    fresh({
      islandSpecifiers: ['@ui/components-loader', '@hooks', '@stores', '@lib/islands/global-config.tsx'],
    }),
    tailwindcss(),
    // Plugin to stub out all core-js imports
    {
      name: 'initialize-routes',
      enforce: 'pre',
      buildStart() {
        initializeRoutes('painel');
      },
    },
    {
      name: 'stub-core-js',
      enforce: 'pre',
      resolveId(id) {
        // Only stub actual core-js module imports, not anything that includes 'core' in the name
        if (id.startsWith('core-js/modules/') || id === 'core-js' || id.startsWith('core-js/internals/')) {
          return '\0virtual:empty-module';
        }
      },
      load(id) {
        if (id === '\0virtual:empty-module') {
          return 'export default {};';
        }
      },
    },
  ],
  resolve: {
    alias: {
      // Rollup (via @fresh/plugin-vite) doesn't apply CJS default interop for these helpers.
      // canvg imports the CJS helpers with `import x from ...`, so point to the ESM variants.
      '@babel/runtime/helpers/defineProperty': '@babel/runtime/helpers/esm/defineProperty',
      '@babel/runtime/helpers/asyncToGenerator': '@babel/runtime/helpers/esm/asyncToGenerator',
      // Replace ALL @babel/runtime-corejs3 imports with @babel/runtime (no core-js)
      '@babel/runtime-corejs3/helpers/defineProperty': '@babel/runtime/helpers/esm/defineProperty',
      '@babel/runtime-corejs3/helpers/asyncToGenerator': '@babel/runtime/helpers/esm/asyncToGenerator',
      '@babel/runtime-corejs3/helpers/esm/defineProperty': '@babel/runtime/helpers/esm/defineProperty',
      '@babel/runtime-corejs3/helpers/esm/asyncToGenerator': '@babel/runtime/helpers/esm/asyncToGenerator',
      // Redirect any core-js module imports to empty module
      'core-js/modules/es.regexp.to-string.js': 'data:text/javascript,export default {}',
      'core-js/modules/es.array.iterator.js': 'data:text/javascript,export default {}',
      'core-js/modules/es.object.to-string.js': 'data:text/javascript,export default {}',
      'core-js/modules/es.promise.js': 'data:text/javascript,export default {}',
      'core-js/modules/es.string.iterator.js': 'data:text/javascript,export default {}',
      'node:module': 'node:module',
    },
  },
  optimizeDeps: {
    esbuildOptions: {
      treeShaking: true,
    },
  },
  ssr: {
    external: [
      'jspdf',
      'quill',
      'quill-magic-url',
      'dompurify',
      'html2canvas',
      'canvg',
      '@babel/runtime-corejs3',
    ],
  },
  server: {
    port: 8000,
  },
});
</details> ```

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions