vitejs/vite · error · Error

Cannot import non-asset file

Error message

Cannot import non-asset file ${specifier} which is inside /public. JS/CSS files inside /public are copied as-is on build and can only be referenced via <script src> or <link href> in html. If you want to get the URL of that file, use ${injectQuery(specifier, 'url')} instead.

What it means

importAnalysis rejects bare imports of files that live in the /public directory. Public files are served/copied verbatim and are not module-graph entries, so importing them as JS/CSS would break the build. The guard checks specifiers starting with '/' that resolve via checkPublicFile and are not assets (assetsInclude) or already ?url-qualified.

Solutions

  1. Move the file out of /public into src (or another source dir) so it becomes a normal module.
  2. If you only need the URL, import with the ?url suffix: `import url from '/file.js?url'` (the message suggests injectQuery(specifier, 'url')).
  3. Reference public JS/CSS via <script src>/<link href> in index.html instead of importing them.
  4. If the file is genuinely an asset, ensure config.assetsInclude matches its extension so the guard treats it as an asset.

Example fix

// before
import data from '/config.json'
// after — move config.json to src/, then
import data from './config.json'
// or just get the URL
import dataUrl from '/config.json?url'
Defensive patterns

Strategy: validation

Validate before calling

// Lint source for imports that resolve into /public
import { checkPublicFile } from 'vite'
function isPublicImport(specifier, config) {
  return specifier[0] === '/' && checkPublicFile(specifier, config)
    && !config.assetsInclude(specifier) && !specifier.includes('?url')
}
if (isPublicImport(specifier, config)) throw new Error('Use ?url or move out of /public')

Type guard

function isPublicNonAsset(specifier, config): boolean {
  return specifier.startsWith('/')
    && checkPublicFile(specifier, config) !== undefined
    && !config.assetsInclude(specifier.replace(/\?.*$/, ''))
    && !/\?url$/.test(specifier)
}

Prevention

When it happens

Trigger: Code does `import x from '/data.json'` or `import './public/script.js'`-style references where the target is actually in the public dir. The check fires when specifier starts with '/', resolves to a public file, and is not classed as an asset or marked with ?url.

Common situations: Developers putting shared runtime JS/JSON in /public intending to import it, or moving a file into /public without updating imports. Also triggered by absolute '/...' imports that coincidentally match a public filename.

Related errors


AI-assisted analysis of vitejs/vite@b4d66fee14 (2026-08-11). Data as JSON: /api/errors/a68f57a6541a8d91. Report an issue: GitHub.

Appendix: source

Thrown at packages/vite/src/node/plugins/importAnalysis.ts:578

              if (isBuiltin(environment.config.resolve.builtins, specifier)) {
                return
              }
            }
            // skip client
            if (specifier === clientPublicPath) {
              return
            }

            // warn imports to non-asset /public files
            if (
              specifier[0] === '/' &&
              !(
                config.assetsInclude(cleanUrl(specifier)) ||
                urlRE.test(specifier)
              ) &&
              checkPublicFile(specifier, config)
            ) {
              throw new Error(
                `Cannot import non-asset file ${specifier} which is inside /public. ` +
                  `JS/CSS files inside /public are copied as-is on build and ` +
                  `can only be referenced via <script src> or <link href> in html. ` +
                  `If you want to get the URL of that file, use ${injectQuery(
                    specifier,
                    'url',
                  )} instead.`,
              )
            }

            // normalize
            let [url, resolvedId] = await normalizeUrl(specifier, start)
            resolvedId = resolvedId || url

            // record as safe modules
            // safeModulesPath should not include the base prefix.
            // See https://github.com/vitejs/vite/issues/9438#issuecomment-1465270409
            config.safeModulePaths.add(fsPathFromUrl(stripBase(url, base)))

View on GitHub (pinned to b4d66fee14)