How to Use Outlined SVG Text in Next.js

Next.js gives you several ways to render text: CSS with next/font, inline SVG, external SVG files, or even canvas. Most of the time, next/font is the right answer. But there's a specific category of typographic use cases where outlined SVG paths beat live text — and if you're building a Next.js site with a custom wordmark, animated hero type, or a decorative text element that needs to look identical across every browser and device, this workflow is worth knowing.
This article covers when and why to use outlined SVG text, how to generate it, and the exact patterns for using it in a Next.js project — including performance, accessibility, and SSR considerations that most tutorials skip.
Who This Is For
You're building a Next.js app (Pages Router or App Router) and you have at least one of these situations:
- A logo or wordmark that uses a font you can't or don't want to load as a webfont
- A hero heading with a visual effect (gradient, stroke, image fill, per-letter animation) that CSS alone can't cleanly achieve
- A decorative text element that must render pixel-identically across browsers, including older ones
- You're generating SVG dynamically at build time and want outlined text in the output
Why Outlined SVG Text Solves Problems Live Text Can't
The rendering consistency problem
Even with next/font doing everything right — preloading, font-display swap, subsetting — there's a window during page load where either a fallback font shows or text reflows slightly as the custom font loads. For body copy, this is an acceptable tradeoff. For a hero wordmark or a precisely composed typographic layout, it's not.
Outlined SVG paths contain zero font references. The shapes are baked-in geometry. They render identically in Chrome, Safari, Firefox, and every other browser from the first paint, with no layout shift.
Effects that CSS text can't do cleanly
Continuous gradient across a multi-letter word
CSS background-clip: text works, but it's a hack — it clips a background to text bounds and requires -webkit-text-fill-color: transparent. Cross-browser support is inconsistent, and it breaks in certain SSR hydration scenarios. An SVG linearGradient applied to a path is the spec-compliant way to do this.
Stroke-only (hollow) type
CSS has no text-stroke standard (only the non-standard -webkit-text-stroke). In SVG, fill="none" stroke="currentColor" just works.
Precise letter-level animation
Animating individual letters with Framer Motion or CSS keyframes on live text requires either splitting text manually into <span> elements (fragile, doesn't respect kerning) or using a JS library like GSAP SplitText. If each letter is already a separate SVG <path>, you animate directly.
Generating the Outlined SVG
Use texttosvg.app — it runs in the browser, needs no account, and exports clean SVGs with no <text> or <font> elements.
Settings that matter for Next.js use:
| Setting | Recommended Value | Why |
|---|---|---|
| Units | px | Matches Next.js / CSS coordinate space |
| Bezier Accuracy | Medium | Smooth curves without excess anchor points |
| Separate Characters | Off (default) | Single compound path; on only if animating letters individually |
| Fill Color | Set to your target color, or leave black and override in CSS | Easier to override with currentColor in JSX |
Download the .svg file. Open it in a text editor and look at what you have. A well-exported SVG from outlined text should look roughly like this:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 420 80">
<path d="M10.5 64V16h8.2l..." fill="#000000"/>
</svg>
No <text>, no <font>, no <defs> pointing to external resources. Just a viewBox, one or more <path> elements, and a fill. That's what you want.
Four Ways to Use It in Next.js
1. Inline SVG in JSX (most flexible)
Paste the SVG content directly into a component. This is the right approach when you need to:
- Override fill color dynamically
- Apply Framer Motion or CSS animations to the paths
- Add
ariaattributes for accessibility
// components/Wordmark.tsx
export function Wordmark({ color = "currentColor" }: { color?: string }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 420 80"
aria-label="YourBrand"
role="img"
width="210"
height="40"
>
<path d="M10.5 64V16h8.2..." fill={color} />
</svg>
);
}
Key points:
- Always add
role="img"andaria-label— outlined paths have no text content for screen readers - Set explicit
widthandheightto prevent layout shift (CLS) - Use
currentColoras the default fill so the component inherits text color from its parent
2. SVG as a static asset in /public (simplest)
Drop the .svg file into /public/images/. Use next/image to render it:
import Image from "next/image";
export function Logo() {
return (
<Image
src="/images/wordmark.svg"
alt="YourBrand"
width={210}
height={40}
priority // if it's above the fold
/>
);
}
next/image handles lazy loading, priority hints, and prevents layout shift automatically. The tradeoff: you can't dynamically change the fill color or animate individual paths this way. Use this for static logos where appearance doesn't change.
3. Import as a React component with SVGR
If your Next.js project uses SVGR (configured in next.config.js), you can import SVG files directly as React components:
// next.config.js
const nextConfig = {
webpack(config) {
config.module.rules.push({
test: /\.svg$/,
use: ["@svgr/webpack"],
});
return config;
},
};
import WordmarkSVG from "@/public/images/wordmark.svg";
export function Logo() {
return <WordmarkSVG aria-label="YourBrand" role="img" width={210} height={40} />;
}
This gives you the best of both worlds: the SVG lives in a file (easy to update), but renders inline in the DOM (animatable and styleable). It's the approach most Next.js projects with multiple SVG assets end up using.
4. Dynamic SVG generation at build time (advanced)
If you're generating open graph images, PDFs, or any file-based output at build time, you can write outlined SVG text directly into generated files. In Next.js App Router, this pairs well with generateStaticParams and route-based OG image generation:
// app/og/route.tsx (Next.js App Router)
import { ImageResponse } from "next/og";
export async function GET() {
return new ImageResponse(
(
<div style={{ display: "flex", background: "#fff", width: "1200px", height: "630px" }}>
{/* Use outlined SVG path directly — no font loading needed */}
<svg viewBox="0 0 420 80" width="420" height="80">
<path d="M10.5 64V16h8.2..." fill="#111" />
</svg>
</div>
),
{ width: 1200, height: 630 }
);
}
ImageResponse uses Satori under the hood, which has its own font-loading mechanism. Bypassing that entirely with outlined paths avoids a class of build-time font errors entirely.
Performance Considerations
Inline SVG vs. external file
Inline SVG adds bytes to your HTML payload but saves an HTTP request. For a single logo path (usually under 2KB of path data), inline is fine. For multiple large SVG graphics, use external files loaded via next/image or SVGR.
Avoid unnecessary complexity in path data
Text exported with maximum Bezier accuracy can generate thousands of anchor points. This bloats path data and slows down paint in the browser. Export at medium accuracy. If path data is still large, run it through SVGO:
npx svgo input.svg -o output.svg
SVGO typically reduces path data by 20–60% without any visible change to the output.
Set explicit dimensions
Unsized SVG elements cause layout shift (CLS penalty in Core Web Vitals). Always set width and height — either as SVG attributes or via CSS. The viewBox alone is not enough to prevent CLS.
Accessibility: What You Must Do
Outlined SVG paths are invisible to screen readers. This is the most important thing to get right:
// Correct — accessible SVG
<svg role="img" aria-label="YourBrand wordmark" viewBox="0 0 420 80">
<title>YourBrand</title>
<path d="..." />
</svg>
Add both role="img" + aria-label (for broad support) and a <title> element inside the SVG (for SVG-aware assistive tech). If the SVG is purely decorative and there's separate visible text nearby, use aria-hidden="true" instead:
// Purely decorative — hide from screen readers
<svg aria-hidden="true" focusable="false" viewBox="0 0 420 80">
<path d="..." />
</svg>
Never leave the aria attributes off entirely. A screen reader hitting a path-only SVG with no label will either skip it silently or announce something unhelpful like "image."
A Practical Example: Animated Hero Wordmark
Here's a complete component combining everything above — an outlined SVG wordmark with a per-word fade-in using Framer Motion:
"use client";
import { motion } from "framer-motion";
export function HeroWordmark() {
return (
<motion.svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 420 80"
width={420}
height={80}
role="img"
aria-label="YourBrand"
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, ease: "easeOut" }}
>
<title>YourBrand</title>
{/* Gradient definition */}
<defs>
<linearGradient id="wordGradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stopColor="#6366f1" />
<stop offset="100%" stopColor="#ec4899" />
</linearGradient>
</defs>
{/* Outlined text path from texttosvg.app */}
<path d="M10.5 64V16h8.2..." fill="url(#wordGradient)" />
</motion.svg>
);
}
This renders with zero font loading, a left-to-right gradient that flows across the entire word, and a smooth entrance animation — all without a single webfont request.
Common Mistakes
Forgetting viewBox
Without a viewBox, the SVG won't scale. Always preserve the viewBox from the exported file.
Setting fill color inside the SVG and then trying to override with CSS
CSS fill only overrides inline fill attributes if your CSS specificity is high enough, and SVG attribute fill beats CSS in some browsers. The cleanest approach: set fill="currentColor" in the path, then control color from a parent element's color property.
Using outlined SVG for body copy or CMS-rendered text
Don't. Outlined SVG is for designed, static typographic elements. If the text comes from a CMS, changes per user, or needs to be indexable by search engines, use next/font and live text. SVG paths don't contribute to page indexing in any meaningful way.
Not running SVGO before committing to the repo
Raw SVG from export tools often contains redundant attributes, comments, and editor metadata. SVGO strips all of it. Make it part of your asset prep step.
Summary
Outlined SVG text in Next.js is the right tool for a narrow but important set of cases: logos, wordmarks, decorative typographic elements, and type effects that CSS can't cleanly achieve. For everything else, next/font is better.
When you do use outlined SVG:
- Generate it at texttosvg.app with medium Bezier accuracy and px units
- Run it through SVGO before adding it to your project
- Inline it as JSX when you need dynamic styling or animation; use SVGR for multi-SVG projects; use
next/imagefor static-only use - Always add
role="img"+aria-label(oraria-hiddenfor decorative elements) - Set explicit
widthandheightto prevent CLS
The result is a typographic element that loads instantly, looks identical everywhere, and never causes a font-related layout shift — which, on a performance-conscious Next.js site, is exactly what you want.
Try the tool with your own text
Open the main TextToSVG tool to test the settings from this blog post in your own browser, or return to the blog for more workflow articles.