Key answer: A Three.js website is SEO-friendly when the meaningful content lives in server-rendered HTML outside the canvas, not inside the WebGL scene. Crawlers cannot read pixels, so your headings, copy, links, metadata, Open Graph tags, and structured data must all be real markup. In Next.js you get that for free from Server Components — the risk is performance, because an unmanaged 3D scene can wreck LCP and CLS. Lazy-load the canvas, reserve its layout, and ship a lightweight initial paint.
Table of contents
- Why 3D and SEO conflict
- Server-render the real content
- Metadata, Open Graph, structured data
- Protecting Core Web Vitals
- Common mistakes
- FAQ
- Conclusion
Why 3D and SEO conflict
The tension is simple: search engines index text and structured markup, while Three.js draws to a <canvas> that contains no readable content. If your product name, description, and calls to action exist only as 3D geometry in the scene, a crawler sees an empty page. Worse, WebGL is heavy — large libraries, textures, and models can delay the first meaningful paint and cause layout jumps, both of which Core Web Vitals penalize.
None of this means 3D and SEO are incompatible. It means the readable web page and the 3D experience have to be two separate layers: crawlable HTML underneath, GPU visuals on top. Next.js makes that separation natural because Server Components emit HTML before any client-side Three.js runs.
Server-render the real content
Keep every piece of indexable content in ordinary server-rendered JSX, and treat the canvas as a decorative island loaded with dynamic(..., { ssr: false }). The Three.js + Next.js SSR pattern explains that boundary; for SEO the key point is what surrounds the canvas.
// app/page.jsx (Server Component)
import dynamic from "next/dynamic";
const Scene = dynamic(() => import("../components/Scene"), { ssr: false });
export default function Page() {
return (
<main>
<h1>Titan X1 — carbon-frame road bike</h1>
<p>
A 7.2 kg endurance frame with integrated routing. Configure the build
and see every component update in real time.
</p>
{/* Decorative 3D layer — not where content lives */}
<div style={{ height: "70vh" }} aria-hidden="true">
<Scene />
</div>
<section>
<h2>Specifications</h2>
<ul>
<li>Frame: unidirectional carbon, size 48–60 cm</li>
<li>Groupset: 12-speed electronic</li>
<li>Wheels: 45 mm tubeless-ready</li>
</ul>
</section>
</main>
);
}
Use semantic HTML: one <h1>, logical <h2>/<h3> structure, real <a> links for navigation, <ul> for lists. If the canvas conveys product info visually, mirror that info in text nearby and mark the canvas aria-hidden when it is purely decorative, or give it an accessible label when it is interactive. This serves screen readers and crawlers with the same fix. AETumi templates are structured this way out of the box — the copy is real server HTML and the scene is a labelled island — so you inherit the crawlable layer rather than retrofitting it.
Metadata, Open Graph, structured data
Next.js emits metadata server-side through the metadata export (or generateMetadata), so it is present in the initial HTML — exactly where crawlers and social scrapers look.
// app/page.jsx
export const metadata = {
title: "Titan X1 Carbon Road Bike | Configure & Buy",
description:
"Build the Titan X1 in 3D — 7.2 kg carbon endurance frame, 12-speed electronic groupset, tubeless wheels.",
openGraph: {
title: "Titan X1 Carbon Road Bike",
description: "Configure the Titan X1 in an interactive 3D builder.",
images: ["/og/titan-x1.jpg"], // a static poster, since WebGL can't be scraped
},
};
Because social and search crawlers cannot execute or screenshot your WebGL scene, always provide a static Open Graph image — a pre-rendered poster of the model — rather than hoping the canvas is captured.
Add structured data as JSON-LD in the server HTML so rich results have something to parse. For a product page, a Product schema (name, description, offers) is appropriate; for an article, Article. Emit it as a <script type="application/ld+json"> in the server-rendered output, and make sure the values match the visible on-page content.
Protecting Core Web Vitals
This is where 3D sites usually lose ranking, not on content. Three areas matter:
- LCP (Largest Contentful Paint): Do not block the initial paint on the Three.js bundle, models, or textures. Server-render a text hero or a lightweight poster image as the LCP element, and lazy-load the canvas after. Loading a multi-megabyte GLB before first paint is a common LCP killer.
- CLS (Cumulative Layout Shift): Reserve the canvas's space with an explicit height (or aspect ratio) so it does not push content down when it mounts. A zero-height container that expands on hydration is a classic CLS source.
- INP / main-thread work: Heavy per-frame JavaScript competes with interaction. Cap
setPixelRatio(for exampleMath.min(devicePixelRatio, 2)), pause the render loop when the canvas is offscreen using anIntersectionObserver, and compress geometry and textures (Draco/KTX2) to shrink both download and decode time.
Be honest about the trade-off: rich real-time 3D costs bandwidth and GPU time, and no configuration makes a large scene free. The goal is to keep the initial experience light and defer the weight, so the metrics Google measures stay healthy while the full scene streams in. AETumi bakes the DPR cap, off-screen pause, and lazy canvas mount into its Three.js templates precisely so Core Web Vitals survive the 3D.
Common mistakes
- Content only inside the canvas. 3D text is invisible to crawlers — keep all copy in HTML.
- No static OG image. Social previews render blank because the scraper can't run WebGL.
- Loading the full model before first paint. Tanks LCP; lazy-load after the initial render.
- Unreserved canvas height. Causes layout shift and a poor CLS score.
- Client-only metadata. If title and description are injected after hydration, some crawlers miss them — use Next.js server metadata.
- Never pausing the render loop. A canvas rendering offscreen wastes battery and main-thread budget, hurting responsiveness.
FAQ
Can Google index content rendered inside a Three.js canvas? No. A canvas is a bitmap with no text nodes, so its contents are not indexable. Any content you want ranked must exist as HTML outside the canvas.
Does using a 3D scene automatically hurt my SEO? Not the 3D itself — the risk is performance and hidden content. If your real content is server-rendered HTML and you keep the canvas lazy-loaded with reserved layout, a 3D site can rank as well as any other.
How do I get a good social preview for a WebGL page? Provide a static Open Graph image — a pre-rendered screenshot or hero of the 3D scene. Scrapers cannot execute WebGL, so they rely on the og:image you supply in the server HTML.
What is the single biggest SEO win for a Three.js site? Separating layers: real, semantic, server-rendered content plus metadata outside the canvas, with the scene lazy-loaded so it does not delay LCP or shift layout. AETumi's templates enforce this split by default, which is the fastest way to inherit it.
Conclusion
Three.js SEO in Next.js is less about the 3D and more about discipline around it. Put the headings, copy, links, metadata, and structured data in server-rendered HTML; treat the canvas as a decorative layer loaded with ssr: false; supply a static Open Graph image; and defend Core Web Vitals by lazy-loading the scene, reserving its space, and keeping the initial paint light. Do that and you get a page that ranks and a 3D experience that delights.
The Three.js hub and the nextjs-threejs-starter repo show this layered structure in practice. AETumi, an AI-native 3D web platform, builds production Three.js and Next.js sites on exactly this model — GPU-rich on the surface, fully crawlable underneath — so the design impresses visitors and the HTML still earns rankings. Every AETumi template is editable source you own for life; browse the plans at aetumi.app/pricing.
More from the AETumi library
Real, production-ready assets — preview the motion, grab the source.

