Using a World Map SVG in Next.js Projects

Dropping a world map SVG into a Next.js project trips up more developers than it should, mostly because next/image — the component everyone reaches for first — doesn't handle SVG the way it handles a photo. Once you know why, the fix takes a few minutes. Start with a clean export from World in Dots so you're not also fighting a messy source file while you sort out the integration.
Why next/image Isn't the Default Answer
next/image is built around optimizing raster formats: resizing, converting to WebP/AVIF, and serving the right size per device. SVG doesn't benefit from any of that — it's already resolution-independent — so Next.js's image optimizer largely ignores it. Pass a local SVG to next/image and it will render, but you're not getting any real benefit from the component, and you still need to set unoptimized on some configurations to avoid a build error or a mismatched aspect ratio.
For a static, non-interactive map (say, a hero background), next/image with unoptimized is fine:
import Image from "next/image";
import worldMap from "@/public/images/world-dots.png";
<Image src={worldMap} alt="Dotted world map" unoptimized />
But if you exported your map as SVG and want to style or interact with individual dots or countries, you need a different approach entirely.
Inlining the SVG as a Component
The most flexible pattern is importing the SVG's markup directly as a React component, rather than treating it as an image file. Two common ways to get there:
1. SVGR (via @svgr/webpack) — configure Next's webpack settings so .svg imports become React components automatically:
// next.config.js
module.exports = {
webpack(config) {
config.module.rules.push({
test: /\.svg$/,
use: ["@svgr/webpack"],
});
return config;
},
};
import WorldMap from "@/public/maps/world.svg";
<WorldMap className="w-full h-auto [&_path]:fill-slate-300" />
This gives you a real component with real props, and every <path> or <circle> in the map becomes addressable with CSS, exactly like customizing SVG map colors with CSS already describes — the only difference is the SVG now lives in your component tree instead of a static file.
2. Raw string import + dangerouslySetInnerHTML — useful if the SVG is generated dynamically (for example, fetched from an API or built at request time) rather than bundled at build time. This avoids a webpack loader change but means you're responsible for sanitizing the source, since injecting untrusted SVG markup this way is a real XSS vector.
App Router: Server vs. Client Components
If your map needs hover states, tooltips, click handlers, or any state at all, the component that renders it must be a Client Component — add "use client" at the top of the file. A common mistake in the App Router is building an interactive map component and forgetting that its parent page is still a Server Component by default; Next.js will error the moment you try to attach an event handler in a server-rendered tree.
A clean split looks like this: keep the page itself as a Server Component (for fast initial load and SEO), and isolate only the interactive map into its own client-marked component that the page imports. This keeps the rest of the page — text, metadata, layout — server-rendered while the map handles its own interactivity on the client.
For a purely decorative background map with no interaction at all, there's no reason to add "use client" anywhere; a plain inline SVG or an <Image> component works fine inside a Server Component.
Performance Notes
A world map with a few thousand dots produces a correspondingly large SVG. A few things matter more in Next.js specifically than in a plain static site:
- Don't bundle a huge inline SVG into every page that imports it. If only one route uses the map, dynamic-import the component with
next/dynamicso it doesn't inflate the JS bundle for pages that never render it. - Static generation is your friend. If the map's content doesn't depend on request-time data, let it render at build time (the App Router does this automatically for static routes) rather than regenerating it on every request.
- Simplify before you inline. The same advice from optimizing SVG maps for web performance applies directly — a map exported with fewer, larger dots parses and paints faster than one with excessive point density, and the difference is more noticeable once the SVG is part of your JS bundle rather than a cached static asset.
Final Thoughts
The short version: use next/image for a map you'll never touch again, and inline the SVG as a component the moment you need to style, animate, or interact with it. Getting this distinction right up front saves you from the classic symptom of a Next.js map project — a map that looks fine but silently ignores every CSS rule you throw at it because it's still sitting behind an <img> tag.