Importing Raw File Contents in Vite Using the ?raw Suffix

Importing Raw File Contents in Vite Using the ?raw Suffix

If you are using window.fetch at runtime just to read a local SVG or Markdown template bundled inside your Vite project, you are adding unnecessary network latency.

const response = await fetch('/templates/terms.md');
const content = await response.text();

Putting raw text files or icon files into your static folders and issuing an asynchronous request to read their contents introduces state management headaches, layout shifts while the text loads, and unnecessary HTTP round-trips for files that never change between deployments.

Vite includes a straightforward, built-in feature designed to solve this exact problem: the ?raw import query. It allows you to import the textual contents of any file straight into a JavaScript string at build time without installing extra plugins or writing custom build scripts.

The Pitfall of Fetching Local Text Assets at Runtime

A common pattern when working with Vite is putting text files into the public/ directory and reading them when a component mounts:

// Avoid doing this for local static templates
import { useEffect, useState } from 'react';

export function DocumentViewer() {
  const [content, setContent] = useState('');
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch('/templates/terms.md')
      .then((res) => res.text())
      .then((text) => {
        setContent(text);
        setLoading(false);
      });
  }, []);

  if (loading) return <p>Loading document...</p>;
  return <div className="prose">{content}</div>;
}

While treating files as static public assets makes sense in certain contexts, as covered in thee post on public versus src assets in Vite—using fetch for bundled content forces the browser to make an extra round-trip just to retrieve bytes that could have been inlined during your build.

How the ?raw Import Query Works in Vite

Vite uses Rollup under the hood during production builds and an ES-module dev server during development. When you append ?raw to an import path, Vite intercepts the module resolution pipeline.

Instead of parsing the file as JavaScript, CSS, or a static asset URL, Vite reads the target file from the filesystem and exports its complete content as a default string export.

Because this process happens entirely during compilation and bundling, there is no runtime HTTP call. The file content is baked directly into your emitted JavaScript bundle.

Practical Example: Inlining SVG Icons and Markdown

Consider an SVG icon that you need to style dynamically with CSS variables or manipulate via the DOM. If you load it using an <img> tag, you cannot easily manipulate its internal paths with CSS. Importing it as raw text lets you inject it directly as inline SVG markup:

import checkmarkSvg from './assets/icons/checkmark.svg?raw';

export function StatusIcon() {
  return (
    <span
      className="icon"
      dangerouslySetInnerHTML={{ __html: checkmarkSvg }}
    />
  );
}

The same technique works cleanly for raw documentation files, shaders, or local database schemas in full-stack setups:

import schemaSql from './db/schema.sql?raw';
import releaseNotes from './docs/v1-notes.md?raw';

console.log(typeof schemaSql); // "string"
console.log(releaseNotes.slice(0, 100));

Vite handles the path resolution just like any other module in your source directory. If the file path is incorrect, you get an explicit build-time or compilation error instead of an unexpected 404 at runtime.

TypeScript Configuration for Query-Based Asset Imports

If you work in a TypeScript project, importing paths with custom query parameters like ?raw can cause TypeScript to complain with Cannot find module ... or its corresponding type declarations.

Vite includes built-in client types that resolve this. Ensure your tsconfig.json references Vite's client types in the compilerOptions block:

{
  "compilerOptions": {
    "types": ["vite/client"]
  }
}

Vite's type definitions declare wildcards for *?raw, telling the TypeScript compiler that any import containing the ?raw suffix resolves to a default export of type string.

When to Use ?raw Versus Regular Asset URLs

While ?raw is convenient, inlining every asset into your JavaScript files has tradeoffs:

  • Use ?raw for small SVG icons, SQL migrations, configuration templates, or Markdown documents that are required immediately when the component renders.
  • Use standard asset imports (e.g., import iconUrl from './icon.svg') when the browser should cache files independently, or when dealing with large assets where inlining would bloat your JavaScript bundle and slow down initial bundle parsing.

By leveraging ?raw for your local text files, you eliminate unnecessary fetch logic and state flags, keeping your components synchronous, lean, and deterministic.

Stay Sharp. Weekly Insights.
New posts, framework updates and weekly software conversations.

No spam. Unsubscribe anytime.
Author profile picture
Walt is a software engineer, startup founder and previous mentor for a coding bootcamp. He has been creating software for the past 20+ years.
No comments posted yet
// Add a comment
// Color Theme

Custom accent
Pick any color
for the accent