JavaScript SEO: Making React and Next.js Sites Crawlable

In this article10 sections
JavaScript SEO is the practice of making sure pages built with JavaScript can be crawled, rendered and indexed by search engines as reliably as plain HTML pages. Google runs JavaScript using a recent version of Chromium, so a React or Vue application is not invisible to it.
But rendering adds a step, it can be delayed, and many common patterns hide content, links or errors from crawlers: content that only appears after a click, routes that use URL fragments, "not found" views returned with a 200 status, and metadata set too late or inconsistently. The most robust approach for content that should rank is to deliver meaningful HTML from the server, through server-side rendering or static generation, and to treat client-side JavaScript as an enhancement.
This guide follows Google's JavaScript SEO documentation and shows how the advice applies to React and Next.js projects.
Key takeaways
- Google processes JavaScript pages in three phases: crawling, rendering and indexing.
- Server-side rendering or static generation is the most reliable way to make content indexable.
- Use real links with href attributes and History API routing, not fragments or click handlers.
- Return proper status codes; a client-side "not found" view with a 200 creates soft 404s.
- Test with the URL Inspection tool to see the HTML Google actually rendered.
How Google processes JavaScript
Google's JavaScript SEO basics describe three phases. First, Googlebot crawls the URL and reads the initial HTML response. Second, pages are queued for rendering, where a headless Chromium executes JavaScript and produces the final DOM. Third, Google indexes the rendered HTML and follows links found in it.
| Phase | What Google sees | What can go wrong |
|---|---|---|
| Crawling | The raw HTML response and HTTP headers | Blocked scripts or APIs in robots.txt, wrong status codes |
| Rendering | The DOM after JavaScript runs | Errors in scripts, content waiting for user actions, timeouts |
| Indexing | The rendered content, links and metadata | Thin content, missing links, conflicting canonical or robots rules |
If the initial HTML already contains the main content, links and metadata, the rendering phase changes little and the page is understood quickly. If the initial HTML is an empty shell, everything depends on rendering working correctly.
Rendering is also a shared, limited resource on Google's side. Pages may wait before they are rendered, and very heavy pages may not finish everything within the time available. Neither is a reason to avoid JavaScript, but both are good reasons to put the important content in the server response.
Choosing a rendering approach
The rendering strategy is the most important JavaScript SEO decision, and it is easier to choose well at the start than to change later.
- Client-side rendering (CSR): the server sends a minimal shell and the browser builds the page. Simple to host, but crawlers and slow devices wait for JavaScript before seeing content.
- Server-side rendering (SSR): the server builds HTML for each request. Content is immediately visible to users and crawlers.
- Static site generation (SSG): HTML is built ahead of time. Very fast and reliable for content that changes occasionally.
- Incremental or on-demand regeneration: static pages refreshed in the background, a good fit for catalogues and blogs.
Google describes server-side rendering, static rendering and hydration as good options, and treats dynamic rendering, which serves a different version to bots, as a workaround rather than a long-term solution. For public marketing pages, product pages and articles, choose SSR or SSG. Client-side rendering is fine for dashboards and logged-in areas that do not need to rank.
Mixed approaches are common and sensible. A store might generate category and product pages statically, render the cart and checkout on the client, and use server rendering for search results that change with every query. Decide per route, based on whether the page needs to be found in search and how often its content changes.
JavaScript SEO in Next.js
Next.js makes the server-first approach the default. With the App Router, components are server components unless marked otherwise, so content is rendered to HTML on the server. The Next.js rendering documentation explains static and dynamic rendering, and how the framework chooses between them.
// app/services/[slug]/page.tsx: content and metadata rendered on the server.
export async function generateMetadata({ params }) {
const service = await getService(params.slug);
return {
title: service.seoTitle,
description: service.seoDescription,
alternates: { canonical: `https://example.com/services/${service.slug}` },
};
}
export default async function ServicePage({ params }) {
const service = await getService(params.slug);
if (!service) notFound();
return <article><h1>{service.title}</h1>{/* ... */}</article>;
}
- Fetch indexable content on the server, not in client components after mounting.
- Generate metadata per route with the metadata API, so titles, descriptions and canonicals are in the initial HTML.
- Call notFound() for missing records, which returns a real 404 status.
- Keep client components for interactivity, such as filters, carts and forms.
Our guide to headless commerce explains when a separate JavaScript front end makes sense for an online store.
Links and routing
Google discovers pages by following links it finds in the rendered HTML. It only follows proper anchor elements with an href attribute. Google's guidance on making links crawlable is explicit about this.
<!-- Crawlable -->
<a href="/services/magento-development">Magento development</a>
<!-- Not reliably crawlable -->
<span onclick="go('/services/magento-development')">Magento development</span>
<a href="#/services/magento-development">Magento development</a>
- Use the framework's link component, which renders a real anchor with an href.
- Use the History API for routing, giving each view its own path, not a fragment after a hash.
- Make pagination and "load more" buttons reachable through linked URLs as well, so every item can be discovered.
- Do not hide navigation behind interactions that crawlers will not perform.
Status codes in single-page applications
Single-page applications often return a 200 status for every URL and then render "page not found" on the client. Google may treat these as soft 404s, or worse, index thin error pages. Our guide to HTTP status codes and SEO explains why the code matters.
- With SSR or SSG: return a real 404 from the server for unknown routes and missing records.
- With pure CSR: Google suggests either redirecting to a URL where the server returns 404, or adding a noindex robots meta tag to the error view with JavaScript.
- For redirects: prefer server-side redirects; JavaScript redirects work, but they are slower and less reliable signals.
Content, metadata and lazy loading
Content that loads only after a user scrolls, clicks a tab or types into a search box may never be seen by Google. Rendering does not simulate those interactions.
- Tabs and accordions: render the content in the HTML and toggle visibility with CSS, rather than fetching it on click.
- Lazy-loaded images: use native
loading="lazy"or an observer-based approach that does not require scrolling events. - Infinite scroll: back it with paginated URLs that can be linked and crawled.
- Metadata: set titles, descriptions, canonicals and robots rules in the initial HTML; changing them with JavaScript is supported but less reliable.
- Structured data: include JSON-LD in the server response where possible.
Resources, performance and robots.txt
Google needs access to the same JavaScript, CSS and API endpoints a browser uses to render the page. Blocking script directories or API paths in robots.txt can leave Google with an empty page. Our guide to robots.txt and noindex explains how to block only what is safe to block.
Heavy JavaScript also hurts real users. Large bundles delay interaction and harm Core Web Vitals, particularly Interaction to Next Paint on mid-range phones. Split bundles by route, defer non-critical scripts and avoid shipping libraries the page does not use. Measure with real-user data where possible, because a fast developer laptop hides problems that customers on older phones feel immediately. Our article on Core Web Vitals covers the measures in detail.
Common JavaScript SEO mistakes
Most JavaScript SEO problems we see in audits come from a short list of patterns. Each is easy to avoid once the team knows to look for it.
- Empty initial HTML for public pages, with content fetched from an API after the page loads.
- Different content for bots and users, which risks looking like cloaking and is hard to maintain.
- Metadata from the previous route left in place after client-side navigation, so pages share titles and canonicals.
- Hydration errors where server and client output differ, causing content to flicker, disappear or be replaced.
- Cookie or consent walls rendered as full-screen overlays that hide the main content in the rendered snapshot.
- Feature flags and A/B tests that serve Google a variant nobody else sees.
A short checklist in your pull request template, covering status codes, links, metadata and server-rendered content for new routes, prevents most of these from reaching production.
Testing JavaScript SEO
- View the raw HTML with
curlor "view source" and check the main content, links and metadata are present. - Use the URL Inspection tool in Search Console to see the rendered HTML, a screenshot and any resources Google could not load.
- Run the Rich Results Test on pages with structured data.
- Crawl the site with JavaScript rendering enabled and disabled and compare discovered URLs.
- Check status codes for unknown routes and deleted records.
If the rendered HTML in Search Console differs from what users see, investigate script errors, blocked resources and timeouts first.
Add these checks to continuous integration where you can. A simple test that fetches key routes without executing JavaScript and asserts that the H1, the canonical link and the main navigation are present catches the most damaging regressions before they ship.
Frequently asked questions
Can Google index React websites?
Yes. Google renders JavaScript, but server-side rendering or static generation makes indexing faster and more reliable.
Is dynamic rendering still recommended?
Google describes it as a workaround. Server-side rendering, static rendering or hydration are better long-term solutions.
Do hash-based URLs work for SEO?
Not reliably. Use the History API so each view has a normal path without a fragment.
How do I return a 404 in a single-page app?
Return it from the server where possible. Otherwise redirect to a URL that returns 404, or add a noindex tag to the error view.
Building or fixing a React or Next.js site? Our SEO services include JavaScript rendering audits, and our developers can implement server rendering.
Want an expert to look at your store?
Tell us what you are working on. A senior e-commerce consultant will reply within one working day with honest, practical advice. No obligation and no sales script.
- Reply within one working day
- Advice even if we are not the right fit
- Your details are never shared