SEO & Growth

Next.js SEO Audit: 9 Issues I Found and a Fix Checklist

Discover 9 common Next.js SEO issues and how to fix them using the App Router Metadata API, canonical URLs, structured data, robots.txt, sitemaps, and internal link audits. Learn a practical SEO checklist and how to diagnose misleading crawler speed reports.

Code Minerals Team
10/14/2026
10 min read
5 views
Featured cover illustration for blog article: Next.js SEO Audit: 9 Issues I Found and a Fix Checklist - SEO & Growth
SEO & Growth
READ TIME: 10 MIN READ

Next.js SEO Audit: 9 Issues I Found and a Fix Checklist

Launching a website is only the first step. Making sure search engines can discover, understand, crawl, and index its content is equally important.

I was building a blog with Next.js and decided to run a technical SEO audit before launch. The website had 22 pages, and my crawler reported nine types of issues. Some were genuine SEO problems, while others revealed limitations in my auditing process.

This is what the audit uncovered, how I approached each issue, and what I learned while investigating performance. I have also included a practical Next.js App Router SEO checklist, code examples, and additional checks you can use before launching your own website.

Understanding the Setup

The blog uses the Next.js App Router and retrieves article content from a separate API. Most pages use Server Components, allowing the server to render the main content into the HTML response.

This matters because a crawler that examines raw HTML without executing JavaScript may not see content that is only added in the browser. Search engines can render JavaScript in many situations, but server-rendered content makes important information more immediately accessible.

To audit the site, I used a Node.js script that crawls internal links and sitemap URLs, saves the returned HTML, and checks each page against a set of technical SEO rules.

The initial checks included:

CheckWhat it verifies
Page titlesTitles are descriptive and appropriately sized
Meta descriptionsDescriptions are useful and unique
Canonical URLsEach indexable page identifies its preferred URL
HeadingsMain content has a clear heading structure
Image alt textImages have appropriate text alternatives
HTTP statusInternal links do not lead to broken pages
Duplicate metadataPages do not unintentionally share titles or descriptions
Indexing directivesRobots directives match the intended indexing strategy
Structured dataRelevant schema markup is present and valid

Character limits are useful editorial guidelines, not strict Google ranking rules. Similarly, passing an audit does not guarantee rankings. The purpose is to identify technical problems that can interfere with crawling, indexing, accessibility, and search presentation.

1. Fixing Titles That Were Too Short

Some of my page titles looked like About - Blog Wish and Tags - Blog Wish.

These titles were technically valid, but they provided very little information about what visitors would find on the page.

I rewrote static page titles to communicate their purpose more clearly. For example:

Before: Contact - Blog Wish

After: Contact Blog Wish - Corrections, Story Ideas and Questions

The updated version gives users more context before they click.

When writing titles, focus on three things:

  • Describe the page accurately.

  • Place the main topic naturally in the title.

  • Keep titles distinct across pages.

A title around 50–60 characters can be a useful starting point, but actual display length depends on the characters used and the available space in search results. Google may also generate a different title link when it considers another page element more useful.

2. Preventing Titles From Becoming Too Long

Article pages had the opposite problem. A headline combined with the site name sometimes produced a title that was unnecessarily long.

Instead of appending the site name to every title automatically, I introduced a helper that compares several title candidates.

const RANGE = { min: 50, max: 60 };

const distance = (length) =>
  Math.max(
    RANGE.min - length,
    length - RANGE.max,
    0
  );

export const fitTitle = (candidates) => {
  return candidates.reduce((best, title) => {
    return distance(title.length) < distance(best.length)
      ? title
      : best;
  });
};

A category page can provide several possible titles:

const candidates = [
  `${category.name} - Latest Articles, News, Reviews and Guides`,
  `${category.name} - Latest Articles and Guides`,
  `${category.name} - Articles and News`,
];

const title = fitTitle(candidates);

This gives the application flexibility when generating metadata for categories and tags of different lengths.

The important detail is that the helper should be a fallback mechanism, not a replacement for meaningful titles. If no candidate fits the preferred range, choose the clearest title rather than sacrificing accuracy to meet a character count.

3. Improving Meta Descriptions

Some pages had descriptions that were too short to explain their purpose.

For example, Log in to your account. offers little context compared with a description that explains what the page allows a visitor to do.

I improved static descriptions manually and used article-specific summaries for dynamic pages.

A good meta description should:

  • Summarize the page accurately.

  • Explain what the visitor can expect.

  • Include relevant terminology naturally.

  • Avoid repetitive keyword lists.

  • Be unique where the page content is meaningfully different.

A range of approximately 120–160 characters can be a practical writing target, but it is not a mandatory SEO limit. Google may truncate or generate snippets from the page content depending on the search query.

For a blog, article excerpts can be a useful source for descriptions, provided they accurately represent the article and are not repeated across unrelated pages.

4. Fixing Broken Internal Links

Two articles linked to category pages that returned 404 errors.

The original idea was to hide categories without published articles. However, removing an empty category from navigation does not mean every URL pointing to it should become a broken page.

I separated the navigation rule from the page's availability.

For example:

  • Categories with published articles can appear in navigation.

  • Empty categories can be hidden from navigation and the sitemap.

  • An empty category may remain accessible with a useful message and a noindex directive if it is expected to become useful later.

  • Permanently removed pages should return an appropriate status or redirect when a genuinely equivalent replacement exists.

A noindex directive does not fix a broken URL by itself. The page must still be accessible to crawlers for them to read that directive.

The original two broken category pages also triggered duplicate metadata, missing canonical, missing heading, and internal-link warnings. Fixing the underlying page behaviour resolved several audit findings at once.

Lesson: Investigate the cause behind a group of audit warnings before fixing each warning individually.

5. Adding Correct Canonical URLs

Canonical URLs tell search engines which URL should be treated as the preferred version when similar or duplicate pages exist.

In Next.js, the Metadata API supports canonical URLs through alternates.canonical.

For dynamic pages, metadata can be generated using generateMetadata.

export const metadata = {
  title: "Next.js SEO Guide",
  description:
    "Learn practical SEO techniques for Next.js websites.",
  alternates: {
    canonical: "https://example.com/blog/nextjs-seo-guide",
  },
};

For production, replace the example domain and path with the actual preferred URL.

For dynamic article pages, generate the canonical from the article's validated slug or canonical URL stored in your content system.

A reusable metadata helper can keep titles, descriptions, canonical URLs, and social metadata consistent:

export function pageMetadata({
  title,
  description,
  url,
  image,
  index = true,
}) {
  return {
    title,
    description,
    alternates: {
      canonical: url,
    },
    robots: index
      ? undefined
      : {
          index: false,
          follow: true,
        },
    openGraph: {
      type: "article",
      title,
      description,
      url,
      images: image ? [image] : [],
    },
  };
}

Use the correct Open Graph type for each page type, and supply absolute canonical URLs and valid image URLs.

One important detail: avoid defining a single homepage canonical in the root layout as a default for every route. Metadata is inherited, and a page that fails to override the value could accidentally identify the homepage as its canonical.

Also remember that canonical tags are signals, not guarantees. Search engines may select a different canonical based on redirects, internal links, content similarity, and other signals.

6. Handling Image Alt Text Correctly

The cover images on article cards used alt="".

That is not automatically an error. Empty alt text is appropriate for decorative images that do not communicate additional information.

However, an image that contributes meaningful information should have a useful text alternative.

For example:

<Image
  src="/images/nextjs-seo-audit.webp"
  alt="Technical SEO audit dashboard for a Next.js website"
  width={1200}
  height={675}
/>

Use descriptions that explain the image's relevant content rather than stuffing keywords into the alt attribute.

For article thumbnails where the adjacent headline already communicates the same information, an empty alt may be the better accessibility choice. The correct decision depends on the image's purpose and context.

For Next.js projects, also review:

  • Image dimensions to reduce layout shifts.

  • Appropriate image formats and compression.

  • Responsive image sizing.

  • Lazy loading for below-the-fold images.

  • Priority loading for genuinely important above-the-fold imagery.

Alt text, image performance, and accessibility are related but separate checks. A technically complete audit should evaluate them independently.

7. Managing Thin Category and Tag Pages

Tag pages can create a large number of URLs without adding much value.

For example, a tag used by only one article might display a heading, one article card, and very little additional information. If many tags follow the same pattern, the site can accumulate a large collection of low-value archive pages.

My approach was to keep thin tag pages out of search results until they provided more useful content.

A page can use the following robots metadata when it should remain accessible but should not be indexed:

export const metadata = {
  robots: {
    index: false,
    follow: true,
  },
};

In a dynamic route, generate this setting conditionally according to the page's content and indexing policy.

A threshold such as three published articles can be a simple starting point, but it is not a universal SEO rule. Some categories with only two articles may offer meaningful value, while a tag with ten nearly identical entries may not.

Consider whether each archive page provides:

  • A useful introduction to the topic.

  • A meaningful collection of related content.

  • Clear navigation to relevant articles.

  • A distinct purpose that justifies its existence.

Do not automatically apply noindex to every category or tag page. Evaluate their usefulness and role in the site's information architecture.

8. Adding Structured Data With JSON-LD

Structured data helps search engines understand the entities and content represented on a page.

For a blog, relevant schema types may include:

  • Organization for information about the publishing organization.

  • WebSite for the website itself.

  • Article or BlogPosting for individual articles.

  • BreadcrumbList for breadcrumb navigation.

The markup must accurately represent visible page content and should not contain invented information.

A reusable JSON-LD component in React can serialize structured data safely:

export function JsonLd({ data }) {
  const json = JSON.stringify(data).replace(/</g, "\\u003c");

  return (
    <script
      type="application/ld+json"
      dangerouslySetInnerHTML={{
        __html: json,
      }}
    />
  );
}

Example article data:

const articleSchema = {
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  headline: "Next.js SEO Audit: 9 Issues I Found",
  description:
    "A practical guide to auditing SEO issues in Next.js.",
  mainEntityOfPage:
    "https://example.com/blog/nextjs-seo-audit",
  author: {
    "@type": "Person",
    name: "Author Name",
  },
  publisher: {
    "@type": "Organization",
    name: "CodeMinerals",
  },
  datePublished: "2026-10-14",
  dateModified: "2026-10-14",
};

Replace the example URL, author, publisher, and dates with verified production values. Add an image property when a representative article image is available.

After implementing structured data, validate the markup with Google's Rich Results Test where applicable and use Schema.org's validator for broader schema checks.

Valid JSON-LD does not guarantee a rich result or higher rankings. Google decides which supported enhancements, if any, to display.

9. Generating robots.txt and XML Sitemaps

The final gaps were the robots file and XML sitemap.

Next.js App Router supports generating both through special files.

For example, app/robots.js can return a robots configuration:

const robots = () => ({
  rules: {
    userAgent: "*",
    allow: "/",
    disallow: [
      "/admin/",
      "/login/",
      "/register/",
    ],
  },
  sitemap: `${process.env.APP_URL}/sitemap.xml`,
});

export default robots;

This generates the robots file using the configured application URL.

Keep in mind that robots.txt is not an access-control mechanism. Private dashboards and authenticated content must be protected by authentication and authorization. Also, blocking a URL in robots.txt can prevent a crawler from seeing its noindex directive, so use these rules deliberately.

The sitemap should contain the canonical URLs of pages that you want search engines to discover and consider for indexing.

For example, app/sitemap.js can build the list from the same content API used to render articles:

export default async function sitemap() {
  const articles = await getPublishedArticles();

  return articles.map((article) => ({
    url: `${process.env.APP_URL}/blog/${article.slug}`,
    lastModified: article.updatedAt,
  }));
}

Here, getPublishedArticles() represents your own API or data-fetching function.

In a complete implementation, include the appropriate published static pages and useful category pages as well. Exclude drafts, unintended duplicate URLs, and pages that should not be indexed.

A sitemap helps search engines discover URLs; it does not guarantee that every listed URL will be indexed.

Why My SEO Crawler Reported Every Page as Slow

After fixing the SEO issues, I added response-time measurements to the crawler.

The initial results looked alarming:

Load timePagesShare
Under 0.5 seconds14.5%
0.5–1 second00%
1–2 seconds00%
2–3 seconds00%
3–5 seconds2195.5%
Over 5 seconds00%

Twenty-one out of 22 pages appeared to take around four seconds. The homepage was the only fast page.

Instead of immediately optimising the application, I investigated how the measurements were collected.

The homepage was requested first, while the remaining pages were fetched in a large batch. The crawler was making approximately 30 simultaneous requests against a development server.

I then tested the application under different conditions:

TestObserved time
One page at a time0.27–0.68 seconds
API request on its own0.003–0.04 seconds
Concurrent requests to one page1.3 seconds fastest, 4.1 seconds median, 6.3 seconds slowest

These were observations from the original test environment, not production benchmarks.

The API was responding quickly on its own, and individual page requests were substantially faster when tested sequentially. The concurrent workload was creating a misleading picture of how quickly a visitor would receive a page.

The lesson was simple: an audit tool measures the whole test setup, not just your website's underlying performance.

I adjusted the crawler to control concurrency and compared individual requests separately.

Development mode also introduces additional work, including on-demand compilation. For a more realistic assessment, build the application and run the production server:

npm run build
npm run start

Then measure representative URLs under controlled conditions.

Even a production server can slow down under excessive concurrency, limited resources, rate limits, or API bottlenecks. The goal is not to assume the crawler is wrong; it is to isolate the cause before changing the application.

Going Beyond Response Time: Core Web Vitals

A page returning HTML quickly is not necessarily a good user experience.

Response time is only one part of performance. You should also investigate how quickly the main content appears, how responsive the page feels, and whether the layout shifts unexpectedly.

Google's Core Web Vitals focus on three important user-experience metrics:

MetricWhat it measuresGood target
Largest Contentful Paint (LCP)Loading performance2.5 seconds or less
Interaction to Next Paint (INP)Responsiveness200 milliseconds or less
Cumulative Layout Shift (CLS)Visual stability0.1 or less

These thresholds are assessed at the 75th percentile of page visits, segmented by mobile and desktop where applicable.

For a Next.js blog, start with the following checks.

Improve LCP

  • Optimise the main article image.

  • Avoid unnecessarily slow server-side data fetching.

  • Review caching and content delivery.

  • Ensure the main content is not delayed by avoidable client-side JavaScript.

Improve INP

  • Reduce expensive JavaScript execution.

  • Avoid unnecessary client components.

  • Break up long-running tasks.

  • Investigate third-party scripts and interactive widgets.

Improve CLS

  • Define image dimensions or aspect ratios.

  • Reserve space for advertisements and embedded content.

  • Avoid inserting banners above existing content without allocating space.

Use PageSpeed Insights, Chrome DevTools, and Search Console's Core Web Vitals report where available. Lab tests help reproduce problems, while field data reflects the experiences of real users when sufficient data is available.

Do not optimise based on a single Lighthouse score alone. Test important page templates on mobile and desktop and investigate the actual bottlenecks.

Additional Next.js SEO Checks Before Launch

Once the original nine issues were addressed, I would also verify the following areas before making the site publicly available.

Verify the rendering output

Open the page source and confirm that important article content, headings, links, and metadata are present in the HTML response.

Test representative article, category, tag, and static pages rather than checking only the homepage.

Review redirects and HTTP status codes

Check that:

  • Published pages return the expected success status.

  • Permanently moved URLs use appropriate permanent redirects.

  • Removed pages do not redirect indiscriminately to the homepage.

  • Internal links point directly to the preferred destination wherever practical.

Validate indexability

A page can be technically accessible but still be excluded from search results.

Review robots meta tags, X-Robots-Tag headers, canonical URLs, authentication requirements, and accidental staging-site restrictions. Confirm that pages intended for indexing can be crawled and that the intended production version is publicly accessible.

Check social sharing metadata

Review Open Graph and Twitter card metadata so that article links display suitable titles, descriptions, and images when shared.

Verify that page-level metadata does not accidentally discard required site-wide properties through object replacement.

Inspect the production sitemap

Open the generated sitemap and verify that its URLs use the production domain, resolve correctly, and match your canonical URL strategy.

Submit the sitemap in Google Search Console and investigate errors or unexpected exclusions.

Monitor after launch

An initial audit is only a snapshot. After launch, monitor indexing, crawl errors, page performance, and organic search visibility. Repeat the audit whenever you introduce significant routing, metadata, or content-model changes.

A Practical Next.js SEO Checklist

Use this checklist before launching a new Next.js website.

  • Confirm that important content is present in the rendered HTML.

  • Generate unique titles and useful meta descriptions.

  • Set appropriate canonical URLs on indexable pages.

  • Review heading hierarchy and image alt text.

  • Fix broken internal links and incorrect HTTP status codes.

  • Check indexing directives and robots.txt.

  • Generate an accurate XML sitemap.

  • Add relevant, validated structured data.

  • Review category and tag pages for meaningful content.

  • Validate redirects and preferred URL formats.

  • Check Open Graph and social sharing metadata.

  • Build the application and test production performance.

  • Review Core Web Vitals and responsive layouts.

  • Inspect the site in Google Search Console after launch.

Frequently Asked Questions

Are 50–60 characters required for an SEO title?

No. It is a useful editorial guideline, not a strict ranking rule. Search result display depends on available space and other factors, and Google may generate a different title link.

Should every image have alt text?

Every informative image should have an appropriate text alternative. Decorative images can use an empty alt attribute, alt="", so assistive technologies can ignore them.

Should tag pages always be indexed?

No. Index pages that provide useful, distinct value. Thin archives may be excluded from indexing, but the decision should depend on their content and purpose rather than an arbitrary article count alone.

Why does an SEO crawler report that every page is slow?

Possible causes include excessive concurrency, development-mode compilation, server resource limits, rate limiting, and slow backend requests. Test individual URLs and compare production measurements before making changes.

Do I need the next-seo package for the App Router?

Not necessarily. Next.js provides a built-in Metadata API for titles, descriptions, canonical URLs, Open Graph metadata, and robots directives. Special files can generate robots.txt and XML sitemaps.

Can I audit a website running on localhost?

Yes. You can inspect HTML, metadata, headings, image attributes, and links. However, a local development server is not a reliable substitute for production performance testing.

Does a valid sitemap guarantee indexing?

No. A sitemap helps search engines discover URLs, but indexing depends on additional factors such as accessibility, content quality, canonical signals, and search engine processing.

Does structured data improve rankings automatically?

No. Structured data helps search engines interpret page content and may enable eligible search enhancements. Valid markup does not guarantee rich results or a ranking improvement.

Final Thoughts

The biggest lesson from this audit was that technical SEO is not simply about making every tool report a green status.

Some warnings pointed to real problems, such as broken internal links and missing canonical URLs. Others exposed weaknesses in the audit logic or highlighted decisions that required context, such as empty alt attributes and thin category pages.

For Next.js developers, a reliable SEO process combines server-rendered content, accurate metadata, sensible indexing rules, valid structured data, clean internal linking, and production performance testing.

Start with the issues that prevent important pages from being discovered or understood. Then measure the user experience, validate the fixes, and continue monitoring the site after launch.

A clean technical foundation will not guarantee search rankings, but it gives your content a better opportunity to be discovered, crawled, and understood.

Further Reading and Official Resources

📢 SHARE THIS ARTICLE WITH YOUR NETWORK:

WANT TO BUILD A SIMILAR SOLUTION?

Our software guild engineers custom web applications, SaaS platforms, and AI automation engines tailored to your business goals.