Skip to content
SVGtoReact

SVG in Next.js: Inline, img, or Component

Choose how to use SVG in Next.js: an img tag from public, next/image, an inline server component, SVGR with webpack or Turbopack, or the app/icon.svg file.

By SVG to React. Published . Updated .

Next.js gives you more ways to use an SVG than plain React does. A file in public/ can be served as an image. next/image accepts SVG with some caveats. Server Components can render inline SVG without sending its code to the browser. You can import .svg files as components with SVGR, and the App Router reads an icon.svg from the app folder. This post explains what each option does and when to pick it. The examples use the App Router.

Option 1: an img tag from public/

Anything in public/ is served from the site root, so public/logo.svg is available at /logo.svg:

SiteLogo.tsx
// public/logo.svg is served at /logo.svg
export function SiteLogo() {
  return <img src="/logo.svg" alt="Acme" width={120} height={32} />;
}

This is the cheapest option. The SVG is not part of any JavaScript bundle, the browser caches it like any other file, and scripts inside the file do not run when it is loaded as an image. Set width and height so the browser reserves space and the layout does not shift when it loads. The limit is that the page cannot style the inside of the image. A hover color or a dark-mode fill has to be a second file or a different technique. With the default ESLint config, Next.js may warn about a plain img and suggest next/image. For SVG the warning has little value, for the reasons in the next section.

Option 2: next/image

SiteLogo.tsx
import Image from "next/image";
import logo from "./logo.svg"; // static import: src, width, and height

export function SiteLogo() {
  return <Image src={logo} alt="Acme" />;
}

// Or from public/, with an explicit size
<Image src="/logo.svg" alt="Acme" width={120} height={32} />

next/image exists mainly to resize and re-encode raster images. SVG is a vector format, so there is nothing to resize, and SVG can contain script. The Next.js documentation says the default loader does not optimize SVG, and that an src ending in .svg is treated as unoptimized automatically. The file is served as it is. The component still gives you a consistent API, lazy loading by default, and width and height from a static import. That is reasonable if the rest of your images go through next/image too.

You can force SVG through the optimizer with images.dangerouslyAllowSVG. If you do, follow the documented advice and set contentDispositionType: "attachment" and a contentSecurityPolicy that blocks scripts, especially when the SVG comes from users or remote hosts. For your own static logos there is rarely a reason to.

Option 3: an inline component, rendered on the server

When the SVG needs to inherit text color, change on hover, or take props, render it inline as a component. In the App Router, components are Server Components unless a file says "use client". A component that only returns SVG markup needs nothing from the client:

CheckIcon.tsx and page.tsx
// app/components/CheckIcon.tsx. No "use client": this is a Server Component.
import type { SVGProps } from "react";

export function CheckIcon(props: SVGProps<SVGSVGElement>) {
  return (
    <svg viewBox="0 0 24 24" width="1em" height="1em" fill="none"
      stroke="currentColor" strokeWidth={2} aria-hidden="true" {...props}>
      <path d="m5 12 5 5L20 7" />
    </svg>
  );
}

// app/page.tsx
import { CheckIcon } from "./components/CheckIcon";

export default function Page() {
  return <p className="text-green-700"><CheckIcon /> Saved</p>;
}

The SVG appears in the server-rendered HTML, and the component's code is not shipped to the browser. The icon costs bytes in the HTML of every page that uses it, but no JavaScript. That is a good trade for small icons and a bad one for a large illustration repeated on many pages. If you import the same component from a Client Component, it becomes part of that client bundle. That is fine, but it is worth knowing where the cost lands.

To get the component code, paste the file into the SVG to TSX converter, or the SVG to JSX page for JavaScript projects. Both produce a component with no hooks or event handlers, so it works as a Server Component as is. The pages of this site are built the same way. Its blog pages, including this one, use no page-specific client components.

Option 4: import .svg files as components with SVGR

If you prefer to keep .svg files in the repository and import them, add the @svgr/webpack loader. The configuration depends on the bundler. Next.js 16 uses Turbopack by default, and many projects on earlier versions still build with webpack. With Turbopack, the Next.js documentation shows this rule:

next.config.js (Turbopack)
// next.config.js: Turbopack rule (Next.js 15.3 and later)
module.exports = {
  turbopack: {
    rules: {
      "*.svg": { loaders: ["@svgr/webpack"], as: "*.js" },
    },
  },
};

The top-level turbopack key replaced experimental.turbo in Next.js 15.3. Older versions use the experimental key. With webpack, the rule must coexist with Next.js's built-in handling of .svg imports for next/image. SVGR's documentation gives a rule that excludes SVG from the built-in loader and adds a ?url escape hatch:

next.config.js (webpack)
// next.config.js: webpack rule, adapted from the SVGR documentation.
// Keeps "import url from './a.svg?url'" working for next/image.
module.exports = {
  webpack(config) {
    const fileLoaderRule = config.module.rules.find((rule) => rule.test?.test?.(".svg"));

    config.module.rules.push(
      { ...fileLoaderRule, test: /\.svg$/i, resourceQuery: /url/ },
      {
        test: /\.svg$/i,
        issuer: fileLoaderRule.issuer,
        resourceQuery: { not: [...fileLoaderRule.resourceQuery.not, /url/] },
        use: ["@svgr/webpack"],
      },
    );
    fileLoaderRule.exclude = /\.svg$/i;
    return config;
  },
};
Usage
import Logo from "./logo.svg";          // component, through SVGR
import logoUrl from "./logo.svg?url";   // URL object for next/image (webpack rule above)

<Logo className="h-8 w-auto text-slate-900" />

In TypeScript, declare the *.svg module so the import is typed as a component. The declaration is shown in SVG to TSX: Typed SVG Icons in React. Remember that SVGR runs SVGO by default, and SVGR 8 uses SVGO 3, which removes viewBox and <title> unless you override those plugins. The details are in SVGR vs Online Converter.

Option 5: icon.svg as the favicon

The App Router's metadata file conventions include an icon file. Put an icon.svg in the app directory, and Next.js adds the matching link tag to every page:

File layout
app/
  icon.svg         -> <link rel="icon" href="/icon.svg?..." type="image/svg+xml" sizes="any">
  apple-icon.png   -> <link rel="apple-touch-icon" ...>
  layout.tsx

Modern browsers accept SVG favicons, and an SVG icon can include a prefers-color-scheme media query in a <style> element to switch colors in dark mode. Apple touch icons do not accept SVG, so add an apple-icon.png as well, or an apple-icon.tsx that generates the PNG. This site uses an icon.svg with a generated apple-icon.

Choosing between them

Whichever you pick, optimize the source file first. The SVG optimizer removes editor metadata in the browser and keeps the viewBox. Then check the result where it is used, not only on its own.

Questions

Does next/image optimize SVG files?

No. SVG is a vector format, so there is nothing to resize, and the Next.js docs say an src ending in .svg is treated as unoptimized automatically. To send SVG through the optimizer anyway you must set dangerouslyAllowSVG, ideally with a strict contentSecurityPolicy.

Does an inline SVG component add client JavaScript in the App Router?

Not by itself. A component without state, effects, or event handlers can be a Server Component, so it is rendered to HTML and its code is not sent to the browser. It becomes client code only when it is imported from a file marked with use client.

How do I import .svg as a component with Turbopack?

Install @svgr/webpack and add a turbopack.rules entry in next.config that maps *.svg to that loader with as set to *.js. The top-level turbopack key replaced experimental.turbo in Next.js 15.3. Webpack builds need a separate webpack rule.

Can I use an SVG as the favicon?

Yes. In the App Router, a file named icon.svg in the app directory is picked up by the metadata file conventions, and Next.js adds the link tag for it. Keep a PNG apple-icon as well, because Apple touch icons do not use SVG.