Most developers treat SEO as someone else's job, something marketing handles with spreadsheets and keyword tools. But a large part of SEO is plain engineering: how your pages are rendered, what your HTML says, how fast it loads, and whether search engines can crawl it. Those are things only the developer can fix.
This guide covers the technical SEO setup for a Next.js App Router project. It will not promise you first place on Google, since rankings depend on content and competition too, but it will make sure the technical side is never the thing holding you back.
What Search Engines Need From You
| Goal | Your job as a developer |
|---|---|
| Be crawlable | Real HTML, working links, robots.txt, sitemap |
| Be understandable | Good titles, descriptions, headings, structured data |
| Be fast | Good Core Web Vitals, optimized images and fonts |
| Be trustworthy | HTTPS, no broken pages, clean redirects, canonical URLs |
| Be shareable | Open Graph and Twitter cards for link previews |
Rendering Matters
Search engines can run JavaScript, but content that is in the HTML on first load is indexed more reliably and faster. With the App Router, server components render HTML on the server by default, which is a big SEO advantage over a client-only single-page app.
- Keep your main content in server components.
- Avoid hiding important text behind client-only fetches after load.
- Use real
<a href>links, not click handlers on divs, so crawlers can follow them. - Use static generation or caching for pages that rarely change.
Metadata: Titles and Descriptions
The title and description are the first things a person sees in search results. In Next.js you define them with the Metadata API. A global default goes in the root layout:
// app/layout.tsx
import type { Metadata } from 'next'
export const metadata: Metadata = {
metadataBase: new URL('https://yourdomain.com'),
title: {
default: 'Your Name | Full-Stack Developer',
template: '%s | Your Name',
},
description: 'Full-stack developer building fast web apps with Next.js.',
openGraph: {
type: 'website',
siteName: 'Your Name',
},
}
For dynamic pages like blog posts, use generateMetadata so each page gets its own title, description, and image:
// app/blog/[slug]/page.tsx
export async function generateMetadata({ params }) {
const { slug } = await params
const post = await getPost(slug)
return {
title: post.title,
description: post.excerpt,
alternates: { canonical: `/blog/${post.slug}` },
openGraph: {
title: post.title,
description: post.excerpt,
type: 'article',
publishedTime: post.date,
images: [post.coverImage],
},
}
}
Writing Good Titles and Descriptions
- Keep titles around 50 to 60 characters so they are not cut off.
- Keep descriptions around 140 to 160 characters.
- Put the main topic near the start of the title.
- Make every page unique. Duplicate titles confuse search engines and users.
- Write the description for a human. It is an advert for the click.
Canonical URLs
The same page can be reachable at several addresses: with and without a trailing slash, with tracking parameters, or on both www and the bare domain. A canonical tag tells search engines which one is the real version. Set alternates.canonical on every indexable page and redirect the duplicates to it.
Sitemap and Robots
The App Router can generate both files from code. A sitemap lists the pages you want indexed:
// app/sitemap.ts
import type { MetadataRoute } from 'next'
import { getAllPosts } from '@/lib/posts'
export default function sitemap(): MetadataRoute.Sitemap {
const base = 'https://yourdomain.com'
const posts = getAllPosts().map((post) => ({
url: `${base}/blog/${post.slug}`,
lastModified: new Date(post.date),
}))
return [
{ url: base, lastModified: new Date() },
{ url: `${base}/blog`, lastModified: new Date() },
...posts,
]
}
// app/robots.ts
import type { MetadataRoute } from 'next'
export default function robots(): MetadataRoute.Robots {
return {
rules: { userAgent: '*', allow: '/', disallow: ['/api/', '/admin/'] },
sitemap: 'https://yourdomain.com/sitemap.xml',
}
}
After deploying, submit the sitemap in Google Search Console and check the coverage report for pages that are excluded or erroring.
Structured Data (JSON-LD)
Structured data gives search engines an explicit description of your content, which can unlock rich results. For a blog post, use the BlogPosting type:
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'BlogPosting',
headline: post.title,
description: post.excerpt,
image: `https://yourdomain.com${post.coverImage}`,
datePublished: post.date,
author: { '@type': 'Person', name: 'Your Name' },
}
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
)
| Schema type | Use it for |
|---|---|
Person | Portfolio or about page |
WebSite | Your home page |
BlogPosting | Articles |
BreadcrumbList | Navigation trails |
FAQPage | Question and answer sections |
Product | E-commerce items |
Always validate your markup with Google's Rich Results Test before shipping.
Social Preview Cards
When someone shares your link on WhatsApp, LinkedIn, or X, the preview image and title come from Open Graph tags. A missing image makes your link look unfinished. Use a 1200 by 630 pixel image, and in Next.js you can generate one per page with an opengraph-image file or point to your cover image in the metadata.
Core Web Vitals
Page experience is part of how search engines judge pages, and it matters even more for real users. The three metrics to watch:
| Metric | What it measures | Good target | Common fix |
|---|---|---|---|
| LCP | How fast the main content appears | Under 2.5 seconds | Optimize and prioritize the hero image |
| INP | How quickly the page responds to input | Under 200 ms | Reduce heavy JavaScript on interaction |
| CLS | How much the layout jumps | Under 0.1 | Set image sizes, reserve space, use font fallbacks |
Quick Wins in Next.js
- Use
next/imagewith propersizes, and addpriorityto the main above-the-fold image. - Use
next/fontto self-host fonts and avoid layout shift. - Load third-party scripts with
next/scriptand the right strategy. - Lazy-load heavy components that are not visible on first load.
- Keep animations on transform and opacity so they stay smooth.
Page Structure and Content
- Use exactly one
h1per page that describes the topic. - Use
h2andh3in order. Do not skip levels for styling. - Write descriptive
alttext for meaningful images and empty alt for decorative ones. - Use clean, readable URLs like
/blog/seo-for-developers. - Link related pages to each other with descriptive anchor text.
Common SEO Mistakes
- Leaving
noindexon after launch from a staging setup. - Blocking the whole site in
robots.txtby accident. - Using the same title and description on every page.
- Rendering key content only on the client.
- Forgetting to redirect old URLs when changing routes.
- Huge uncompressed images that wreck LCP.
- Never checking Search Console.
Launch Checklist
- Unique title and description on every page.
- Canonical URLs set, duplicates redirected.
sitemap.xmlandrobots.txtlive and submitted.- JSON-LD on posts and the home page, validated.
- Open Graph image on every shareable page.
- Lighthouse and real-user Core Web Vitals checked on mobile.
- Site added to Google Search Console and Bing Webmaster Tools.
The Verdict
Technical SEO is not magic. It is a checklist that takes an afternoon to implement and pays off for as long as the site exists. Get the foundations right, then spend your real effort on content worth ranking.
Your code decides whether search engines can find, read, and trust your pages. Your writing decides whether anyone clicks. You need both.
