π Next.js SEO β A Clean & Practical Checklist
If you want your Next.js site to actually show up on Google (and look great when shared on social), this is what you should do β step by step.
π·οΈ Meta Tags β Why They Matter
Meta tags give search engines and social platforms info about your page.
If you skip them, your site might look like a blank page on Google or Twitter.
π Always add these basic meta tags:
Tag | Why it's important |
---|---|
title | The title of your page (what people see on Google results) |
description | Short description (also visible in search results) |
keywords | Optional, not used much by Google, but harmless |
robots | Tells crawlers if they should index your page |
viewport | Controls how your site looks on mobile devices |
charSet | Character encoding (UTF-8 is standard) |
π Open Graph meta tags: (for social sharing)
Tag | Why it's important |
---|---|
og:site_name | Name of your site |
og:locale | Language/locale |
og:title | Title when shared on social |
og:description | Description on social |
og:type | website or article |
og:url | URL of the page |
og:image | Image shown in preview (use PNG/JPG β no WebP!) |
og:image:alt | Alt text for accessibility |
og:image:type | Image type (image/png) |
og:image:width + og:image:height | Image dimensions |
π Article-specific Open Graph tags:
(important for blog posts, articles, news)
Tag | Why it's important |
---|---|
article:published_time | When it was published |
article:modified_time | Last update time |
article:author | Author name |
π Twitter meta tags: (for Twitter/X previews)
Tag | Why it's important |
---|---|
twitter:card | Large image summary |
twitter:site | Site's Twitter handle |
twitter:creator | Author's Twitter handle |
twitter:title | Title on Twitter |
twitter:description | Description on Twitter |
twitter:image | Image URL (again PNG/JPG) |
βοΈ How to Add Meta Tags in Next.js
π In App Router, define
export const viewport = { width: "device-width", initialScale: 1, themeColor: "#ffffff" };
export const metadata = { title: "Site Title", description: "Short site description", keywords: ["keyword1", "keyword2"], openGraph: { siteName: "My Site", type: "website", locale: "en_US", images: [{ url: "https://yoursite.com/og-image.png", width: 1200, height: 630, alt: "My Site" }] }, twitter: { card: "summary_large_image", title: "Site Title", description: "Short site description", images: [{ url: "https://yoursite.com/og-image.png", width: 1200, height: 630, alt: "My Site" }] }, robots: { index: true, follow: true, googleBot: "index, follow" }, alternates: { canonical: "https://yoursite.com" } };
π On dynamic pages (blog posts), use
export async function generateMetadata({ params }) { const post = await fetch(`YOUR_ENDPOINT/${params.slug}`).then(res => res.json()); return { title: `${post.title} | My Site`, description: post.description, openGraph: { title: `${post.title} | My Site`, description: post.description, type: "article", url: `https://yoursite.com/${post.slug}`, publishedTime: post.created_at, modifiedTime: post.modified_at, authors: ["https://yoursite.com/about"], tags: post.categories, images: [ { url: `https://yoursite.com/assets/${post.slug}/thumbnail.png`, width: 1024, height: 576, alt: post.title } ] }, twitter: { card: "summary_large_image", title: `${post.title} | My Site`, description: post.description, images: [{ url: `https://yoursite.com/assets/${post.slug}/thumbnail.png`, width: 1024, height: 576, alt: post.title }] }, alternates: { canonical: `https://yoursite.com/${post.slug}` } }; }
π JSON-LD Schema β Why Bother?
JSON-LD adds "structured data" for Google.
It helps Google understand your page type (Blog Post, Product, Event...) and improves rich snippets.
π Example for blog post:
const jsonLd = { "@context": "https://schema.org", "@type": "BlogPosting", mainEntityOfPage: { "@type": "WebPage", "@id": "https://yoursite.com/my-post" }, headline: "Post Title", description: "Post description", image: "https://yoursite.com/assets/my-post/thumbnail.png", datePublished: "2024-01-11T11:35:00+07:00", dateModified: "2024-01-11T11:35:00+07:00", author: { "@type": "Person", name: "Your Name", url: "https://linkedin.com/in/yourname" }, publisher: { "@type": "Person", name: "Your Name", logo: { "@type": "ImageObject", url: "https://yoursite.com/avatar.jpg" } }, inLanguage: "en-US", isFamilyFriendly: "true" };
π Render this in your component:
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />
π Use this tool to generate schemas: https://technicalseo.com/tools/schema-markup-generator/
πΊοΈ Sitemap β Why You Need It
Sitemap helps Google crawl all your pages. Without it, some deep links may never get indexed.
π In Pages Router:
Use
npm install next-sitemap npx next-sitemap
π In App Router:
Manually define
export default async function sitemap() { const pages = [ { url: "https://yoursite.com", lastModified: new Date(), changeFrequency: "daily", priority: 1 }, { url: "https://yoursite.com/about", lastModified: new Date(), changeFrequency: "monthly", priority: 0.9 } // more pages ]; return pages; }
π Result will be accessible at:
arduino CopyEdit https://yoursite.com/sitemap.xml
π€ robots.txt β Controlling Crawlers
π In Pages Router:
makefile CopyEdit User-agent: * Disallow: Sitemap: https://yoursite.com/sitemap.xml
π To block certain pages:
Disallow: /search?q= Disallow: /admin
π In App Router:
Define
export default function robots() { return { rules: { userAgent: "*", allow: ["/"], disallow: ["/search?q=", "/admin"] }, sitemap: ["https://yoursite.com/sitemap.xml"] }; }
π Important Link Tags
π Always include these in your head:
Link Tag | Purpose |
---|---|
canonical | Prevent duplicate content issues β tell Google the "main" URL |
alternate | Used for multilingual sites |
icon | Favicon |
apple-touch-icon | iOS home screen icon |
manifest | For PWA support |
Summary β What You Should Do (Minimal Checklist)
β Add full Meta Tags (title, description, OG, Twitter)
β Add JSON-LD schema for key pages
β Generate and serve a sitemap.xml
β Serve a robots.txt
β Include proper link tags (canonical, icon, manifest)
If you do all of this, your Next.js site will:
β Look great in search results
β Show rich previews on social media
β Be fully crawlable and indexable by Google