Technical SEO issues in React and Next.js sites 2026

React and Next.js have become the dominant stack for Indian SaaS products, startup landing pages, and enterprise web apps. They are excellent tools for building fast, interactive UIs — but they introduce a specific class of SEO problems that static sites never face. Developers at Technopark, Infopark, and TIDEL Park who build in Next.js often hand off sites to marketing teams who have no idea these issues exist, and those issues quietly suppress rankings for months. This guide walks through all 21 of them, with diagnosis steps and fixes for each.

Category 1: Rendering Issues (Issues 1–7)

Issue 1: Full Client-Side Rendering for Content Pages

A React SPA that renders content exclusively on the client sends an almost empty HTML shell to Googlebot. Googlebot processes JavaScript, but it does so asynchronously and with resource constraints. Content pages — blog posts, service pages, product pages — should never rely on client-side rendering alone. Diagnosis: View page source (not DevTools inspect) and check if your headings and body text are present. If they are not, you have CSR content. Fix: In Next.js App Router, ensure content pages are Server Components (the default). Remove any 'use client' directive from components that contain primary page content.

Issue 2: getServerSideProps on Static Content Pages

Using getServerSideProps on pages whose content does not change per request forces a server round-trip on every crawl. This increases Time to First Byte and burns crawl budget unnecessarily. Diagnosis: Check your Pages Router pages for getServerSideProps on content that does not require real-time data. Fix: Switch to getStaticProps with revalidate for content that updates occasionally (ISR), or to pure static generation for content that rarely changes.

Issue 3: Hydration Mismatches Breaking Googlebot Rendering

Hydration errors occur when the HTML rendered on the server differs from what React renders on the client. These errors appear in browser console as "Hydration failed" and cause React to re-render the component on the client, replacing server-rendered content. Googlebot may see the server-side version but not the hydrated version — or vice versa. Diagnosis: Open Chrome DevTools Console and look for hydration warning messages. Also check Lighthouse for any rendering errors. Fix: Avoid using typeof window !== 'undefined' checks inside render logic. Use useEffect for browser-only code, or the suppressHydrationWarning prop only when genuinely necessary.

Issue 4: Lazy Loading Above-the-Fold Content

React's React.lazy() and Next.js's next/dynamic are excellent for code splitting — but if you lazy load components that contain your hero text, H1, or primary body content, that content arrives late in the render cycle. Googlebot's rendering timeout means it may never see it. Diagnosis: In Chrome DevTools, throttle the network to Slow 3G and observe which content appears first. If your main content loads after visible UI elements, lazy loading is the likely cause. Fix: Reserve next/dynamic with ssr: false strictly for genuinely interactive widgets below the fold — maps, chat widgets, calculators. Content that matters for SEO should load with the initial server render.

Issue 5: JavaScript Errors Preventing Full Page Render

An uncaught JavaScript error early in the component tree stops React rendering at that point. Googlebot sees a partial page with missing content. This is a silent killer because the error does not show up in Google Search Console as an indexing issue — the page appears indexed but with incomplete content. Diagnosis: Use URL Inspection in Search Console, click "Test Live URL", then "View Tested Page" and switch to the console tab. Any JS errors visible there affect what Googlebot renders. Fix: Add Error Boundaries around major content sections. Use next/error for graceful error handling. Review your Vercel or hosting platform's runtime error logs regularly.

Issue 6: Deferred Data Fetching for SEO-Critical Content

Fetching content inside useEffect hooks means that content is not present in the initial server response. Product descriptions fetched client-side, blog post bodies loaded via API calls in useEffect, and dynamic page titles set after a client-side fetch are all invisible to Googlebot's first-pass HTML evaluation. Fix (App Router): Use async Server Components and fetch data directly in the component without useEffect. Fix (Pages Router): Move data fetching to getStaticProps or getServerSideProps so it arrives with the initial HTML.

Issue 7: Infinite Scroll Without Pagination Fallback

Infinite scroll that only loads content via JavaScript scroll events means Googlebot cannot access the content below the initial viewport. For e-commerce listing pages or blog archives, this effectively hides your inventory from search. Fix: Implement "Load More" pagination with discrete URL-based pages (/products?page=2) as a fallback, or use paginated navigation with proper rel="next" links. Never rely solely on scroll position to trigger content loading.

Category 2: Metadata Issues (Issues 8–13)

Issue 8: App Router vs Pages Router Metadata Confusion

This is the most common source of metadata bugs in migrating Next.js sites. The Pages Router uses next/head: you import Head and place tags inside it. The App Router uses the Metadata API: you export a metadata object or a generateMetadata async function from your page.tsx file. Mixing the two — using next/head in an App Router page — does not work as expected and can result in duplicate or missing tags. Fix: Audit every page in your app directory for legacy next/head imports and replace them with the Metadata API.

Issue 9: Dynamic Pages Missing Unique Titles and Descriptions

Product pages, blog posts, and category pages in Next.js that share a single layout often end up with identical titles and descriptions if generateMetadata is not implemented per-page. This is a structural duplicate content issue that suppresses all affected pages. Fix in App Router: export async function generateMetadata({ params }) { const post = await getPost(params.slug); return { title: post.title, description: post.excerpt }; } Ensure generateMetadata is present in every dynamic route's page.tsx.

Issue 10: Canonical Tag Pointing to Wrong Domain or Version

Next.js projects often run on multiple domains during development — a preview URL (e.g., my-app.vercel.app) plus the production domain. If canonical tags are set dynamically using process.env.NEXT_PUBLIC_URL and that variable is not correctly set in production, canonicals point to the wrong domain. Fix: Hard-code the production URL in your metadata config, or validate that NEXT_PUBLIC_URL is correctly set in your production environment variables before deploying.

Issue 11: Open Graph Images Not Rendering in Social Previews

Next.js App Router supports dynamic OG image generation via opengraph-image.tsx files using the ImageResponse API. When these are misconfigured — wrong dimensions, fonts failing to load, or async errors in the component — the OG image falls back to nothing, harming CTR on social shares. Diagnosis: Test OG images with the Open Graph Debugger tool at developers.facebook.com or with opengraph.xyz. Fix: Ensure your opengraph-image.tsx uses only Edge Runtime-compatible code, loads fonts from the file system rather than external URLs when possible, and exports correct size and contentType values.

Issue 12: Missing or Incorrect robots Meta Tags on Dynamic Routes

In Next.js, if you forget to set the robots metadata field on staging or admin routes, those pages may be indexed. Conversely, overly broad noindex directives in a shared layout can accidentally deindex content pages. Fix: Set explicit robots metadata in your root layout for the default case, then override it in specific pages or route segments where different behaviour is needed. For staging environments, set NEXT_PUBLIC_ROBOTS=noindex and apply it conditionally.

Issue 13: Structured Data in Client Components Not Seen by Googlebot

JSON-LD structured data injected via useEffect or inside a 'use client' component arrives after Googlebot's initial HTML evaluation. Schema that Google never reads provides no benefit. Fix: Place all JSON-LD script tags in Server Components or in the generateMetadata return value (Next.js 14+ supports other fields in metadata for this). The cleanest pattern is a JsonLd Server Component that renders a <script type="application/ld+json"> tag inline.

Category 3: Performance Issues (Issues 14–18)

Issue 14: Unoptimised Bundle Size Hurting LCP

Large JavaScript bundles delay page interactivity and push LCP scores into the "Needs Improvement" range. Next.js's bundle analyser (@next/bundle-analyzer) reveals which packages dominate bundle size. Common culprits in Indian startup Next.js projects: unused chart libraries, full lodash imports instead of individual functions, and date-fns vs moment where moment adds 230KB. Fix: Run ANALYZE=true npm run build with the bundle analyser configured. Identify the top five packages by size and replace or tree-shake them.

Issue 15: LCP Element Not Preloaded

The Largest Contentful Paint element — typically the hero image — should load as early as possible. Next.js's <Image> component supports priority prop which adds a preload link to the document head. Without it, the hero image competes with scripts and stylesheets for bandwidth. Fix: Add priority to the <Image> component for any image that is the LCP element on its page. Only use priority on the above-the-fold image — applying it to multiple images creates competing preloads that cancel the benefit.

Issue 16: Font Loading Causing Layout Shift and CLS

Google Fonts loaded without proper swap behaviour cause text to be invisible until the font loads (FOIT) or to shift layout when it swaps in (FOLS). Both hurt CLS scores. Next.js's next/font (introduced in Next.js 13) handles this correctly by embedding font metrics in CSS and loading fonts from the Next.js server rather than Google's CDN. Fix: Replace <link href="fonts.googleapis.com"> imports with next/font/google. This eliminates the external request and applies size-adjust CSS to prevent layout shift.

Issue 17: Third-Party Scripts Blocking Interaction

Analytics, chat widgets, and ad scripts loaded synchronously in the <head> delay the page's Time to Interactive and inflate INP scores. In Next.js, use the next/script component with the appropriate strategy prop: afterInteractive for analytics, lazyOnload for chat widgets and secondary tools, and worker (with Partytown integration) for scripts that can run off the main thread entirely. Never use beforeInteractive unless the script is genuinely required for the initial render.

Issue 18: Images Missing Width and Height Props Causing CLS

Next.js's <Image> component requires explicit width and height props (or fill with a sized container) to reserve space in the layout before the image loads. Missing these props means the page reflows when the image loads, directly causing CLS. The fill prop with a position: relative parent and explicit aspect ratio is the correct pattern for responsive images where exact dimensions vary.

Category 4: Crawlability Issues (Issues 19–21)

Issue 19: Dynamic Routes Not Included in Sitemap

Static Next.js sitemaps generated at build time from the app/sitemap.ts file will miss dynamic routes whose content is created after deployment — new blog posts, new product listings, new user profile pages. Fix: Implement sitemap.ts as an async function that fetches all dynamic URLs from your CMS or database at build time, or use Incremental Static Regeneration to rebuild the sitemap on a schedule. For very large dynamic sites, generate multiple sitemaps and a sitemap index.

Issue 20: robots.txt Misconfiguration Blocking Crawl

Next.js App Router handles robots.txt through app/robots.ts which exports a MetadataRoute.Robots object. A common mistake is blocking the /_next/static/ path in robots.txt — this prevents Googlebot from fetching the JavaScript bundles it needs to render pages. Another frequent error is deploying a blanket Disallow: / from a staging robots.txt into production. Fix: Always allow /_next/static/. Review your robots.ts export carefully before each production deployment.

Issue 21: Soft 404s on Empty Dynamic Route Pages

When a dynamic route like /blog/[slug] receives a request for a slug that does not exist, Next.js can either return a proper 404 status or silently render an "empty" page that appears to the browser as 200 OK but has no real content. Google classifies these as soft 404s and may deindex or ignore them — but the bigger problem is that they waste crawl budget. Fix: In App Router, call notFound() from the next/navigation package inside generateStaticParams or the page component when the requested resource does not exist. This triggers Next.js's built-in 404 page with the correct HTTP status code.

Frequently Asked Questions

Does Googlebot fully render React and Next.js pages?

Googlebot renders JavaScript, but with important caveats. It uses an older version of Chrome, runs with limited resources, and processes JavaScript asynchronously after an initial crawl. This means content rendered only on the client side may be seen by Googlebot on a second visit, days or weeks after initial discovery. For critical content — titles, headings, body text, internal links — ensure it is present in the initial HTML response, either through SSR or SSG in Next.js.

What is the difference between the App Router and Pages Router for SEO in Next.js?

The App Router uses the Metadata API — exporting a metadata object or generateMetadata function from page.tsx — while the Pages Router uses the next/head component. The App Router also defaults to Server Components, which produce full HTML that Googlebot can read without JavaScript execution. This makes the App Router architecturally better for SEO when used correctly, but it introduces new pitfalls if Client Components are used carelessly for content that should be server-rendered.

How do I debug what Googlebot sees on my React site?

The most accurate method is Google Search Console's URL Inspection tool. Enter a specific URL, click "Test Live URL", then "View Tested Page" and switch to the HTML tab. This shows exactly what Googlebot saw when it last rendered the page. Additionally, disable JavaScript in Chrome DevTools and reload the page — if your content disappears, Googlebot may struggle with it too.

Should I use next-sitemap or build the sitemap manually?

next-sitemap is the recommended approach for most Next.js sites. It automatically generates sitemap.xml based on your route structure, supports dynamic routes via additionalPaths configuration, and handles multi-sitemap splitting for large sites. Avoid manually maintaining a sitemap for Next.js sites with more than 50 pages, as route changes will quickly make it stale.