Next.js SEO: A Complete Practical Guide

  • Web Development
  • Published
  • Updated
  • 5 min read
Next.js SEO: A Complete Practical Guide

Next.js gives you good SEO defaults, which is exactly why so many Next.js sites end up with mediocre SEO: the defaults are good enough that nobody looks closer. This is the checklist I run on every project, ordered by how much each item actually moves the needle.

1. Make sure the content is in the HTML

Everything else is irrelevant if the page renders empty. Check it in one command:

curl -s https://yoursite.com | grep "some text from your page"

If it is missing, your content is client-rendered only. Google can execute JavaScript, but it queues those pages for a second rendering pass that can take days, and other crawlers and AI search engines often do not run JavaScript at all.

Fix it with getStaticProps for content that can be built ahead of time, or getServerSideProps for content that cannot. Static is faster and cheaper; reach for it first.

2. Unique metadata on every single page

The most common real-world failure is a shared layout that sets one title for the whole site. Every route needs its own title, description and canonical URL.

  • Title: 50 to 60 characters. Longer gets truncated in results.
  • Meta description: 120 to 160 characters. Longer gets cut mid-sentence.
  • Canonical: absolute URL, one per page, pointing at itself.
  • Open Graph and Twitter card tags, including an image, so shared links do not look broken.

Build one Seo component that takes title, description and path, and use it everywhere. Then the rule is enforced by the code rather than by discipline.

3. Sitemap and robots.txt in the right place

In the Pages Router, robots.txt goes in the public folder. Putting it in the project root is a silent failure - the file exists in your repository and returns 404 in production. I have seen this in production more than once.

For the sitemap, generate it from your data rather than maintaining a list by hand:

// pages/sitemap.xml.js
export const getServerSideProps = ({ res }) => {
  res.setHeader('Content-Type', 'application/xml');
  res.write(buildSitemapFromYourContent());
  res.end();
  return { props: {} };
};

A hand-maintained sitemap goes stale the first time someone is in a hurry. A generated one cannot.

4. Structured data that describes the actual entity

JSON-LD is how you tell search engines and AI systems what your site is, not just what words are on it. Use a single script tag containing an @graph so the nodes can reference each other by @id.

The types worth having:

  • Person or Organization - who owns this site
  • WebSite - the site itself
  • BreadcrumbList - improves how the URL renders in results
  • BlogPosting or Article on every post
  • FAQPage where you genuinely have questions and answers
  • LocalBusiness if you serve a geographic area

5. Images, which are usually the performance problem

On almost every slow Next.js site I audit, images are the cause. Raw img tags with 2 MB PNG exports are extremely common.

Use next/image and give it the information it needs:

<Image
  src="/images/hero.jpg"
  alt="Descriptive text that says what the image shows"
  width={1364}
  height={1762}
  sizes="(max-width: 768px) 90vw, 550px"
  priority          // only on the LCP image
/>

Two things people get wrong here. First, omitting sizes means the browser downloads a far larger file than it needs on mobile. Second, putting priority on everything defeats the purpose - it belongs on exactly one image, the largest one above the fold.

Also compress the source files. next/image resizes, but it cannot undo a badly exported PNG. Converting screenshots to WebP routinely cuts 90 percent.

6. Core Web Vitals

Three metrics, each with a different usual cause:

LCP - Largest Contentful Paint

Almost always the hero image or a webfont. Fix with priority on the hero image, preconnect to font hosts, and font-display swap.

CLS - Cumulative Layout Shift

Caused by images without dimensions, ads, or fonts swapping at different sizes. Always set width and height, or an aspect-ratio.

INP - Interaction to Next Paint

Caused by heavy JavaScript on the main thread. Audit your bundle, lazy-load anything below the fold, and check whether a UI library is costing more than it earns.

7. Scroll-reveal animations are an SEO trap

Libraries like Framer Motion server-render your content with opacity zero and reveal it with JavaScript. The text is in the HTML, so it is technically indexable, but if the script fails the page is blank for real users.

Two mitigations worth applying:

  • Never animate the hero with JavaScript. Use a CSS keyframe so the LCP element never depends on hydration.
  • Add a noscript style block that forces hidden elements visible.

8. Mistakes I see repeatedly

  • robots.txt in the project root instead of public/ - returns 404
  • Canonical tags pointing at URLs that 404, usually after a slug changed
  • The same meta description on every page
  • Content locked inside modals with no URL, so it can never be indexed or shared
  • alt="image" or alt="error" on every image - technically present, completely useless
  • A hidden h1 with the real heading marked up as h2
  • A root api/ folder on Vercel, which turns plain data files into serverless functions

The order to do this in

  1. Confirm content is server-rendered
  2. Unique title, description and canonical per page
  3. robots.txt and a generated sitemap
  4. Compress images, then convert to next/image
  5. JSON-LD structured data
  6. Core Web Vitals
  7. Then, and only then, worry about keywords

Technical SEO is the foundation. It will not make bad content rank, but bad technical SEO will absolutely stop good content from ranking.

Need help building this?

I take on web app, mobile and e-commerce projects. Tell me what you are building and I will reply within 24 hours with scope, timeline and a fixed quote.

Start a project