M. CRESPO.
Blog
← Back to blog
Technology

πŸ“ˆ Next.js SEO β€” A Clean & Practical Checklist

TechnologyProductivity

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:

πŸ‘‰ Open Graph meta tags: (for social sharing)

πŸ‘‰ Article-specific Open Graph tags:

(important for blog posts, articles, news)

πŸ‘‰ Twitter meta tags: (for Twitter/X previews)


βš™οΈ How to Add Meta Tags in Next.js

πŸ‘‰ In App Router, define viewport + metadata:

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 generateMetadata():


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 next-sitemap


npm install next-sitemap
npx next-sitemap

πŸ‘‰ In App Router:

Manually define app/sitemap.ts:


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

robots.txt tells search engines which pages to crawl or ignore.

πŸ‘‰ In Pages Router:

public/robots.txt:

makefile
CopyEdit
User-agent: *
Disallow:
Sitemap: https://yoursite.com/sitemap.xml

πŸ‘‰ To block certain pages:


Disallow: /search?q=
Disallow: /admin

πŸ‘‰ In App Router:

Define app/robots.ts:


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:


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