Next.js SEO: what the framework does for you and what it does not
You ship with Next.js. You want pages that get crawled, indexed and clicked. Here is what the framework handles for SEO, what it exposes for you, and what no framework can do for you.
By Théophile Louvart, founder of Porteur · Updated 14 September 2026 · Markdown
What Next.js gives you for SEO by default
Next.js App Router ships with good SEO defaults. Pages are statically rendered by default. Dynamic rendering is opt in. Crawlers that do not run scripts can read your HTML.
- Metadata API for titles, canonicals and Open Graph, via metadata objects or generateMetadata
- app/sitemap.ts and app/robots.ts to output XML and robots.txt
- Redirects and rewrites in next.config
- next/image for responsive images and formats
- next/font to self host fonts
- A trailingSlash setting to enforce one URL style
These tools remove boilerplate work. They do not write your titles. They do not decide the canonical. They do not make thin pages rank.
Sitemap and robots: ship the right files from the app
Keep your sitemap small, current and on the same host. Output only indexable URLs. Exclude paginated filters and drafts. Update on deploys or when content changes.
// app/sitemap.ts
import { getAllRoutes } from "@/lib/routes";
export default async function sitemap() {
const base = "https://yourproduct.com";
const routes = await getAllRoutes(); // e.g. ["/","/pricing","/blog/nextjs-seo"]
return routes.map((path) => ({
url: `${base}${path}`,
lastModified: new Date(),
changeFrequency: "weekly",
priority: 0.7
}));
}
// app/robots.ts
export default function robots() {
const base = "https://yourproduct.com";
return {
rules: [{ userAgent: "*" }],
sitemap: [`${base}/sitemap.xml`],
host: "yourproduct.com"
};
}
Test the files in a browser. Check Search Console, Sitemaps, after deploy. If you split sitemaps, link a sitemap index at /sitemap.xml. Keep only canonical URLs in it.
Rendering: static HTML and crawlers that do not run scripts
Static rendering is the default in the App Router. That helps crawlers that fetch HTML only. Your title, canonical and content arrive in the first response.
- Use fetch with cache options to keep a route static when data allows it
- Mark truly dynamic routes with dynamic = "force-dynamic" only when needed
- Avoid client components for content that could be server rendered
- Do not hide primary content behind client side effects
// app/blog/[slug]/page.tsx
export const dynamic = "force-static"; // or omit if static by default
export async function generateStaticParams() {
const slugs = await getAllSlugs();
return slugs.map((slug) => ({ slug }));
}
If a route must be dynamic, keep the HTML meaningful without JavaScript. Render headings and copy on the server. Defer widgets to the client with care.
Redirects, rewrites and site moves
Use redirects for URL changes and migrations. Configure them in next.config so they run at the edge on most platforms. Keep hops to one.
// next.config.js
module.exports = {
async redirects() {
return [
{ source: "/old-pricing", destination: "/pricing", permanent: true },
{ source: "/blog/:slug/", destination: "/blog/:slug", permanent: true },
];
},
async rewrites() {
return [
{ source: "/api/:path*", destination: "https://api.yourproduct.com/:path*" },
];
},
};
Plan a site move like a project. Build a redirect map as a two column table in your repo. Old URL, new URL. Test every mapping before you switch DNS or deploy the change.
Build the map
Crawl the old site, export from its sitemap, and export indexed pages from Search Console. Merge into one list. Map each to one new URL.
Apply and test
Apply redirects in next.config or at your proxy. Script test that each old URL 301s in one hop to a 200 at the new URL.
Switch and monitor
Deploy. Keep the old domain responding with 301s. Submit the old sitemap for a while so Google recrawls and sees the moves.
For a domain change, verify both domains in Search Console. Use Settings, Change of address. It works for domain level moves. Not for path only changes. Expect some fluctuation for weeks. Keep redirects for at least a year, longer if you can. Update internal links, canonicals, hreflang, structured data and sitemaps to the new URLs.
URLs: trailing slashes, www and HTTPS
Pick one URL style and be consistent. Google treats /page and /page/ as different URLs. The root / is the exception. Next.js has a trailingSlash setting.
// next.config.js
module.exports = {
trailingSlash: false, // or true, but choose one and redirect the other
};
Pick www or non www. Redirect the other, on every path, with a 301. Set canonicals to the chosen host. Verify a Domain property in Search Console so you see both.
Move to HTTPS if you are not already. 301 each HTTP URL to its HTTPS twin in one hop. Update internal links, canonicals and sitemaps. Avoid mixed content. Consider HSTS once you are sure.
A fixed page looks like this: /pricing loads at https://www.yourproduct.com/pricing, the non www and HTTP forms redirect to it, and its canonical matches the final URL.
Images and fonts: ship fast, stable pages
next/image helps with responsive sizes and modern formats. It can reduce payload and Largest Contentful Paint. You still choose sizes and placeholders that make sense for your layout.
// app/(marketing)/components/Hero.tsx
import Image from "next/image";
export default function Hero() {
return (
<Image
src="/hero.png"
alt="Dashboard overview"
width={1200}
height={800}
priority
sizes="(max-width: 768px) 100vw, 1200px"
placeholder="blur"
blurDataURL="/hero-blur.png"
/>
);
}
The width and height shown here are examples, not measured on your page. Measure your hero and set exact dimensions for your layout.
Set explicit width and height to avoid layout shift. Use priority on the above the fold hero only. Lazy load the rest by default. Serve real alt text that says the image, not the keyword list.
next/font lets you self host fonts. That helps with privacy, control and performance. Preload the main font weight. Avoid layout shift by using font metrics or fallback settings.
// app/layout.tsx
import { Inter } from "next/font/google";
const inter = Inter({ subsets: ["latin"], display: "swap" });
export default function RootLayout({ children }) {
return <html className={inter.className}><body>{children}</body></html>;
}
A fixed page looks like this: the hero uses next/image with correct sizes, the LCP element loads within 2.5 seconds, and there is no layout jump from fonts or images.
Internal links and content: the work no framework does
Search needs pages worth indexing. That is not a build setting. Write a title that says the page. Put the task or the product in the first heading. Cover the topic fully.
- Link related pages. From /guides/getting-started to /pricing with a clear anchor like "See pricing"
- Fix orphan pages. Add them to navigation or link them from a hub page
- Avoid keyword cannibalisation. One page per topic or intent
- Use descriptive anchors. Not "click here"
- Keep canonicals self referencing, unless you have a true duplicate variant
A fixed page looks like this: /blog/nextjs-seo links to /“Technical SEO checklist for a small site” and /pricing in body copy. The anchor text explains the target. The canonical is set to itself in generateMetadata.
Measure and improve: Core Web Vitals and crawl checks
Check your field data. Core Web Vitals use the 75th percentile of real Chrome users over 28 days. Good thresholds are LCP up to 2.5 s, CLS up to 0.1, INP up to 200 ms.
- Use PageSpeed Insights for field data and a Lighthouse run
- Use Lighthouse locally for lab work. Scores vary by run
- Watch the Search Console Core Web Vitals report for per URL groups
- Use your platform logs or a crawler to find broken links and redirect chains
Lighthouse weights, as of version 10 and later, are TTB 30%, LCP 25%, CLS 25%, FCP 10% and Speed Index 10%. Use it to spot long tasks and render delays. Do not chase the score at the expense of features that users need.
Check a slow page
Open PageSpeed Insights for https://yourproduct.com/pricing. Note the LCP element and its load time. Note any layout shift sources.
Fix what you ship
Compress the hero image, serve the right size, and preload its font. Remove unused third party scripts. Keep INP under 200 ms by trimming expensive event handlers.
Verify in the field
Wait for the 28 day window to reflect changes. Confirm in Search Console, Core Web Vitals, that the URL group moved to good.
Porteur can scan your site and rivals from a URL and show three findings to start. It takes about thirty seconds and is free.
Questions
Yes. App Router pages are static by default, which gives crawlers HTML with titles and content on first fetch. It also exposes metadata, sitemap, robots, redirects, image and font tooling. You still need to write good titles, choose canonicals, and build internal links.
Use a static export when values are known at build, like /pricing. Use generateMetadata when you need params or fetched content, like /blog/[slug]. In both cases set metadataBase and alternates.canonical so URLs resolve correctly.
Prefer server components and static rendering where possible. For truly dynamic routes, render meaningful HTML on the server and hydrate only what needs interaction. Avoid hiding headings and copy behind client effects. Keep query string gates off primary content.
Pick a style and keep it. /page and /page/ are different to Google. Set trailingSlash in next.config and add a redirect for the other form. Keep internal links consistent in code and content.
Aim for LCP up to 2.5 seconds, CLS up to 0.1, and INP up to 200 ms. Field assessment uses the 75th percentile over 28 days. Use PageSpeed Insights for field data and Lighthouse for lab checks.
Build a one to one 301 map, apply it in next.config or at the edge, and keep it live for at least a year. Verify both domains in Search Console and use the Change of address tool. Update internal links, canonicals, hreflang, structured data and sitemaps to the new URLs. Expect some fluctuation for a few weeks.
Sources
Check my site, free
Paste your URL to get a free check of your Next.js site’s metadata, sitemaps, redirects and rivals in about thirty seconds, with three findings you can ship.
- Free check, no card
- Read-only, your own accounts
- Readable by your agent
Read next
- GuideTechnical SEO checklist for a small site
- GuideCanonical tags: what they do and the mistakes that cost rankings
- GuideTrailing slash: two URLs for one page unless you decide
- GuideCore Web Vitals for a product site: the three numbers
- GuideImage optimisation for page speed: the hero image and everything below it
- GuideWeb fonts and page speed: load one face, and load it first
- GuideThe best SEO Chrome extensions: what each shows in one click
- GuideGatsby SEO: static HTML, the Head API, and what to watch