Technical SEO for Next.js: What Actually Moves Rankings
Most Next.js SEO advice is a checklist of tags nobody verifies. Here is what genuinely changes how a site performs in search, in the order it is worth doing.
Technical SEO has a credibility problem. Most of what gets published is a list of tags to add, presented as if adding them is the same as improving anything. Add a meta description. Add Open Graph. Add a sitemap. Done.
Those things matter, but they are the floor, not the work. Here is what actually moves the needle on a Next.js site, roughly in the order I would do it.
1. One canonical URL per page, no exceptions
The single most common technical problem I find is the same page being reachable at several addresses.
With and without a trailing slash. With and without "www". HTTP and HTTPS. With tracking parameters attached. Each variant looks like a separate page to a crawler, and each one splits whatever authority the real page has earned.
In the App Router, every page should declare its canonical explicitly:
- Set metadataBase once in the root layout so relative URLs resolve to absolute ones.
- Set alternates.canonical on every page to the one address you want indexed.
- Pick a trailing-slash convention in next.config and stick to it.
- Redirect the non-canonical host at the DNS or hosting layer, not in application code.
The reason this comes first is that everything else compounds on top of it. There is no point earning links to a page if that authority is being divided across four copies of it.
The practical version: build one helper that generates metadata for a page from a path, and route every page through it. Canonical URLs cannot drift when there is only one place they are constructed.
2. Metadata that is generated, not copy-pasted
Every page needs a title, a description, a canonical, Open Graph tags, and Twitter card tags. Writing those out by hand on twenty pages guarantees that page fourteen has last month's URL in its canonical.
Centralise it. One function takes a title, a description, a path, and some keywords, and returns a complete metadata object with everything filled in consistently. Pages call the function. Nobody hand-writes an og:url again.
This is not a performance optimisation, it is a correctness one. Hand-maintained metadata rots. Generated metadata cannot.
3. Structured data that describes a real entity
Schema markup is where most sites do the least and get the most from fixing it.
The mistake is treating structured data as decoration — sprinkling a Person here and an Article there with no relationship between them. What Google is actually building is an entity graph: who you are, what you run, what you have published, and how those things connect.
The version that works:
- Declare a Person and an Organization node once, in the root layout, each with a stable @id.
- Declare a WebSite node that names the Person as its publisher.
- On each page, add the page-level node — WebPage, ContactPage, Service, Article — and reference the Person and WebSite by @id rather than repeating them.
- Put every profile and property you own into sameAs: social accounts, other sites, subdomains.
The sameAs array is doing more work than people expect. It is how a search engine knows the account on one platform, the site on a subdomain, and the person on this domain are one entity rather than three coincidences.
Then add the page types that earn actual result features. FAQPage markup on a page with a real FAQ section can produce expanded results. BreadcrumbList changes how the URL is displayed in a listing. Both are cheap, and both change what your result looks like on the page.
One rule: the markup must describe what is visibly on the page. FAQ schema for questions that do not appear on the page is a manual action waiting to happen.
4. Ship less JavaScript on the routes that matter
This is where Next.js sites most often lose, and it rarely shows up on a checklist.
The App Router makes it easy to build a page that is technically server-rendered but ships a large client bundle anyway, because one component near the top of the tree is marked as a client component and pulls half the app with it. The HTML arrives fast; the page is not interactive for another two seconds; the largest element paints late.
Concrete things that help more than any tag:
Keep heavy visual libraries out of the initial load. A 3D scene, a chart library, a rich text editor — these belong behind a dynamic import that only resolves when the feature is actually used. If a WebGL background is loading before your headline paints, the background is winning a fight it should not be in.
Push "use client" as far down the tree as it will go. A page does not need to be a client component because one button in it needs state. Move the state into the button.
Watch what a component's imports drag along. An icon library imported without care can pull in the entire set. An animation library imported at the page level applies to the whole page.
The measurable payoff is in Core Web Vitals — LCP and INP specifically. These are ranking signals, but more importantly they correlate with whether a visitor is still there when the page finishes loading.
5. Internal linking with intent
Internal links are the cheapest ranking lever available and the most under-used.
Every page you publish should link to the pages you want to rank, using text that describes what is on the other end. Not "click here" and not the bare URL — the actual subject.
Three structural habits:
Hub pages. A page that links out to a related cluster, and that the cluster links back to. A services page that links to each individual service. A hub for your other properties that links to each of them.
Contextual links in body content. Where an article mentions a concept you have written about in depth, link it. Not fifteen times per article — two or three genuinely relevant ones.
A footer that is a real navigation surface. Every page's footer links to the pages that matter, including any properties on other subdomains that cannot appear in this site's sitemap.
That last point deserves its own line: a sitemap may only list URLs on its own host. If you run other properties on subdomains, real crawlable links from your main site are how they get discovered and associated with you.
6. A sitemap generated from a route registry
Next.js will generate a sitemap from a single file. The trap is hand-maintaining the list of routes inside it — six months later it lists two pages that no longer exist and misses four that do.
Build it from data. Static routes come from one array that is the source of truth. Dynamic routes — blog posts, case studies — get mapped from the same data the pages themselves render from. Add a post, and it is in the sitemap with no separate step.
Set lastModified from the content's real date rather than the build time. A sitemap that claims every page changed today is a sitemap that gets ignored.
7. Robots rules that protect crawl budget
Your robots file should do two things: point at the sitemap, and keep crawlers out of routes with no search value.
Admin routes, API endpoints, and anything behind authentication should be disallowed. Not because they are secret — robots is not a security boundary — but because crawl budget spent on a login screen is crawl budget not spent on your content.
Pair the robots rules with an X-Robots-Tag header on those same routes. A disallowed URL that someone links to externally can still end up indexed; the header is what actually keeps it out.
8. Verify in Search Console, then keep looking
Everything above is invisible until you connect Google Search Console.
Verify ownership, submit the sitemap, and then actually read it. The reports that matter:
Page indexing. Which pages are indexed, and for the ones that are not, why. "Discovered — currently not indexed" usually means thin content or a crawl budget problem. "Duplicate without user-selected canonical" means your canonicals are wrong.
Core Web Vitals. Field data from real visitors, not a lab score. This is what Google actually uses.
Performance queries. The queries you are getting impressions for but no clicks. Those are pages ranking on page two where a title rewrite is worth more than a new article.
Set a reminder to look once a month. The pattern in the data is more useful than any single reading.
The order matters
If you did only three things: fix canonicals, cut the JavaScript on your most important routes, and build a real internal linking structure. Those three account for the majority of the difference between a Next.js site that ranks and one that does not.
The tags are table stakes. The structure is the work.