JavaScript SEO
Is Googlebot Actually Seeing What Your React or Next.js Site Renders?
JavaScript SEO means closing the gap between what a browser renders after your JS runs and what a crawler indexes. On a site rendered in the browser or only partially hydrated, those two can diverge: content that's visible to a visitor can be invisible, delayed, or truncated for the crawler that decides whether the page ranks at all.
The problem
Most technical SEO advice assumes the HTML a server sends is the content search engines see. On sites rendered with JavaScript that assumption breaks: Googlebot has to execute your JS in a second rendering pass before it can index anything rendered in the browser, that pass is queued and can be delayed by days on a large or rarely crawled site, and any JS error, unhandled promise rejection, or blocked resource during that pass can silently drop content Google never retries. A page can look complete in a browser and still be indexed as an empty shell.
Symptoms
Google Search Console's URL Inspection tool shows a rendered screenshot that's missing content visible in a normal browser, or the "Page fetch" step succeeds while "Screenshot" or "More info" flags resources that blocked rendering.
The indexed content of a page (checked via `site:` search or the Inspection tool's rendered HTML) lags noticeably behind what's currently live, even weeks after a content update.
Content behind a fetch in the browser (data loaded after mount, e.g. `useEffect` + `fetch`) never appears in Google's cached or rendered HTML at all.
Internal links rendered only via `onClick` handlers or `<a>` tags injected by JS without a real `href` aren't discovered by crawlers that don't execute every event handler.
Core metadata (title, canonical, meta description) is set in the browser after hydration instead of being present in the initial server response, so what curl or view source shows is inconsistent with what the browser eventually renders.
How we diagnose it
We start with the same test that matters most: fetch the URL the way a crawler does (no browser, no JS execution, just a plain HTTP GET) and diff that against the fully rendered DOM in a headless browser. Anything present in the rendered DOM but absent from the raw response is a candidate for an indexing gap that depends on rendering. We cross check with Search Console's URL Inspection rendered screenshot and "View Crawled Page" HTML, check the Network tab for requests that block rendering or fail during first paint, and review Core Web Vitals field data (see our Core Web Vitals page) since slow hydration and JS SEO problems frequently share a root cause.
The fix
The fix is almost always to move content into the initial server response rather than leaving it dependent on JS executing in the browser: rendering on the server, static generation, or streaming server components for anything that matters for indexing (headings, body copy, canonical and meta tags, internal links with real hrefs). Content that is genuinely interactive only (a dashboard behind auth, for example) doesn't need this; content meant to rank does. We also make sure critical resources (fonts, images above the fold, scripts that block rendering) aren't accidentally disallowed in robots.txt, since a blocked resource during Google's rendering pass can cause the page to be indexed as incomplete even when the HTML itself is fine.
Implementation details
This site is itself built on Next.js's App Router: every canonical page under `src/app/[lang]/[...slug]/page.tsx` is a Server Component whose `generateMetadata()` resolves title, description, canonical and hreflang tags on the server before any client JS runs. It is the same pattern this page describes rather than something we only recommend to clients. Page bodies then hand off to client components (the `"use client"` files under `src/components/PageContent/`) only for interactivity (animations, accordions), not for the text content itself, which arrives in the initial HTML via the translations JSON consumed on the server. For Next.js sites specifically we check: the App Router's static vs. dynamic rendering choice per route (`generateStaticParams` coverage), whether client components are thin wrappers that only hydrate content already rendered on the server, or islands rendered in the browser that hold back content until JS loads, and whether `next/dynamic` imports used for code splitting (see the dynamic loader pattern used throughout this codebase) still render on the server (`ssr: true`) rather than only in the browser.
Common mistakes
Treating "it renders fine in Chrome DevTools" as proof it's indexable. That only confirms the browser can execute the JS, not that Google's rendering pass will pick it up on the same schedule or budget.
Fetching primary page content in the browser (`useEffect` + API call) instead of on the server, so there's a real chance a crawler indexes the loading state instead of the content.
Using `next/dynamic(..., { ssr: false })` for content that matters for SEO, which opts the whole chunk out of server rendering, not just its interactivity.
Blocking JS and CSS bundles in robots.txt (a legacy practice from the era before crawlers rendered pages) that actively prevents Google from rendering the page correctly today.
Assuming a JS framework migration (e.g. moving to a new meta framework or app architecture) preserves SEO by default. Rendering strategy, metadata generation, and route structure all have to be verified again, not assumed.
Validation
We validate with Search Console's URL Inspection tool (both the rendered screenshot and "View Crawled Page" HTML) on a sample of real URLs, a diff of raw HTTP responses against the rendered DOM across the site's main templates, a robots.txt check for accidentally blocked JS and CSS, and Core Web Vitals field data to confirm the fix didn't just move content into the DOM but also kept it fast enough for INP.
Bilingual JS SEO considerations
On a bilingual EN/AR site, JavaScript SEO issues can compound with locale routing: content fetched in the browser after a locale switch, or hreflang alternates computed only in a client effect, can leave one language's rendered response incomplete even when the other locale's is fine. We verify the raw HTTP responses of both locales independently, rather than checking just one and assuming symmetry.
Related service & solutions
JavaScript SEO is the rendering layer underneath schema markup, hreflang, and canonical tags. None of those signals matter if the content and metadata that carry them never reach the crawler intact.
Frequently asked questions
Common questions about JavaScript SEO
Doesn't Google render JavaScript now, so this isn't a problem anymore?
Is rendering on the server always the right fix?
Can you fix JavaScript SEO without a framework migration?
How is this different from Core Web Vitals work?
Want your JavaScript rendering checked against what Google actually indexes?
Request a JavaScript SEO auditYou Might Also Find Useful
How Long Does SEO Take to Work?
SEO takes 3–6 months to show results and 9–12 months for full ROI. Learn the honest timeline, what affects speed, and how to accelerate results in Jordan.
How Much Does SEO Cost? — Pricing Guide for Jordan & MENA
SEO in Jordan costs $500–$1,500/month for meaningful results. Learn what drives pricing, what you're actually paying for, and how to avoid low-quality SEO traps.
SEO vs SEM: What's the Difference?
SEO builds free organic rankings over months. SEM (Google Ads) buys instant placement. Learn the key differences and which strategy makes sense for your business.