Case study: Gumlet turned ChatGPT mentions into 20% of inbound revenue. Read it →
Next.js SEO for B2B SaaS: 11 Rendering, Canonical & Crawl Traps to Fix
Next.js SEO for B2B SaaS breaks at four separate layers: HTTP response, raw HTML, rendered DOM and crawl discovery. DerivateX tests each layer independently, because a page Googlebot indexes can still return an empty shell or a 403 to OAI-SearchBot. This guide covers 12 traps, each with a test and the evidence that proves the fix.
- A page that looks correct in Chrome proves almost nothing. Chrome runs JavaScript, accepts cookies, passes the bot challenge at the edge and follows client-side navigation. Most crawlers do none of those things in the same combination.
- Google documents that Googlebot processes JavaScript. No engine publishes a promise that its retrieval crawler will render your React tree, so treat rendering as something you verify per user agent, not something you assume.
- 80% of URLs cited by ChatGPT and Perplexity are not in Google’s top 100, and 28% of ChatGPT-cited pages have zero organic Google visibility. Google indexation is not a proxy for AI crawler access.
- The 12 traps below cluster into three groups: rendering (client-side data, Suspense fallbacks, stale ISR, frozen static exports, misplaced metadata), canonicals and duplicate URLs (metadataBase, trailing slashes, redirect chains, soft 404s), and crawl access (non-anchor navigation, middleware and WAF blocks, build-time sitemaps).
- Every fix needs evidence in one of three places: a raw HTTP response, the rendered DOM, or an access log line showing the user agent and status code. A screenshot of a browser is not evidence.
- DerivateX runs this as a repeatable pass or fail diagnostic before any content or citation work starts, because publishing into a site that returns skeleton HTML to retrieval crawlers wastes the budget.
Why can Google see a Next.js page that ChatGPT cannot?
Because they are different systems with different jobs, different infrastructure and different tolerance for cost. Google’s crawling and rendering pipeline is documented, mature and designed to handle JavaScript at web scale. Google’s own JavaScript SEO basics documentation explains how Googlebot crawls, renders and indexes JavaScript pages, and it tells you which tools reproduce that behavior. That documentation exists for one engine only.
The retrieval crawlers that feed AI answers are a mixed set. OpenAI maintains a publishers and developers FAQ covering how its crawlers identify themselves and how publishers can control access, and it names distinct user agents for distinct jobs, including OAI-SearchBot for search and GPTBot for training collection. PerplexityBot is a separate agent operated by a separate company with separate infrastructure. Assuming they all behave like Googlebot is the single most expensive assumption in this entire subject, and it is the assumption most Next.js deployments are built on.
DerivateX sees this play out in a specific pattern with software companies at $5M to $50M ARR. The marketing site was rebuilt on Next.js, which positions itself as the React framework for the web and offers per-page rendering and caching options including Incremental Static Regeneration. Everything renders beautifully. Google indexes it. Then someone types the category into ChatGPT and the pricing page, the integrations page and the comparison pages are simply not in the answer, because the crawler that fetched them got a shell with a loading skeleton and a script tag.
Only 11% of domains are cited by both ChatGPT and Perplexity. That overlap is low partly because of editorial and retrieval differences, and partly because the technical access surface is inconsistent from one crawler to the next. Two of the three might get your HTML and one might not, and nothing in your analytics will tell you which is which unless you go looking in the logs.
How do I test Next.js SEO B2B SaaS?
A Next.js SEO B2B SaaS audit is a four-layer test, run in order, where a pass at one layer never stands in for a pass at another. To test it yourself, do these four things on every commercial URL. First, fetch the URL with curl once per crawler user agent and record the status code. Second, grep the raw HTML body for a literal price or plan name. Third, diff the raw HTML against the rendered DOM and write down every commercial fact that exists only in the delta. Fourth, filter 30 days of access logs for named AI crawler user agents and check which status codes they received. DerivateX runs this sequence on every technical engagement before touching content, because each layer fails in a way the layer above it hides.
| Layer | What you are testing | How to test it | What a pass looks like |
|---|---|---|---|
| Layer 1: HTTP response | Whether the crawler is allowed to fetch the URL at all | curl -sI -A "OAI-SearchBot" https://yoursite.com/pricing, repeated for GPTBot, PerplexityBot and a plain curl agent | Status 200, no challenge redirect, no x-robots-tag: noindex |
| Layer 2: Raw HTML | Whether the commercial content exists before JavaScript runs | curl -s -A "OAI-SearchBot" https://yoursite.com/pricing | grep -i "per month" | Prices, plan names, feature rows and headings appear in the response body |
| Layer 3: Rendered DOM | Whether the content only exists after hydration | Compare view-source against the inspected DOM, or run a headless fetch with and without JavaScript enabled | The delta between raw HTML and rendered DOM contains no commercial facts |
| Layer 4: Discovery and logs | Whether crawlers can find the URL and are actually reaching it | Filter server or CDN access logs by user agent string, path and status code | Named AI crawler user agents appear with 200 responses on money pages |
The rendered DOM is the document structure that exists after scripts execute, as described in the MDN Web Docs reference material for JavaScript and the web platform. The gap between raw HTML and rendered DOM is the exact size of your exposure. If your pricing table lives only in that gap, every retrieval system that does not execute JavaScript sees a page about nothing.
One caution on tooling. Testing with a spoofed user agent tells you what your server and edge return to that string. It does not tell you what the real crawler does with the response afterward. Those are two different questions, and only the first one is under your control, which is why DerivateX treats the raw response as the deliverable and the citation as the target.
Which Next.js rendering traps hide commercial content from AI crawlers?
Five rendering patterns account for most of what DerivateX finds on Next.js marketing sites. All five look correct in a browser and all five ship through code review without comment, because nothing about them is a bug in the application sense.
Trap 1: Commercial content fetched client-side inside a client component
The pattern looks like this. Pricing tiers, integration directories, customer counts and comparison tables are pulled from a CMS or an API inside a component marked "use client", with a useEffect fetch. The server sends a page frame and a spinner.
- Test: Run
curl -s https://yoursite.com/pricingand search the output for a literal price string or plan name. - Expected evidence of failure: The response contains the layout, the nav and the footer, but zero occurrences of the price, and a
__NEXT_DATA__or flight payload with no product fields. - Fix: Move the fetch into a server component or a route that renders on the server, so the price is in the first byte of HTML. Next.js supports server data fetching directly inside async components, so this is usually a relocation rather than a rewrite.
- Re-test: The same curl command returns the price string, and the count of matches equals the number of plans on the page.
Trap 2: Content trapped behind a Suspense fallback
Next.js ships dynamic HTML streaming integrated with the App Router and React Suspense. Streaming is good for perceived speed and bad for anything that fetches a slow upstream, because the first flush contains the skeleton and the real content arrives in a later chunk that a simple fetcher may never assemble.
- Test: Measure the initial flush against the complete response. Capture the first chunk with
curl -s --no-buffer --max-time 1 https://yoursite.com/pricing | wc -cand the full body withcurl -s --max-time 10 https://yoursite.com/pricing | wc -c. If the initial flush is under 60% of the final byte count, the commercial content is gated behind a Suspense boundary. - Expected evidence of failure: The first flush is well under that threshold, and grepping it for a price string or a feature row returns nothing while the full body returns matches.
- Fix: Keep commercially decisive content outside Suspense boundaries. Stream the testimonial carousel and the usage calculator, and do not stream the plan comparison.
- Re-test: The initial flush is at or above 60% of the final byte count and already contains the full content block, with only secondary elements arriving in later chunks.
Trap 3: Incremental Static Regeneration serving months-old HTML
ISR is a per-page caching option, and a long revalidate value on a low-traffic page means the cached HTML can be older than your last three pricing changes. Crawlers then quote a number you no longer charge. DerivateX has seen SaaS pricing pages serve superseded tiers for weeks because nothing triggered a regeneration.
- Test: Check response headers for cache age and compare the rendered price against the current source of truth in your CMS.
- Expected evidence of failure: A high age value with content that does not match the CMS record.
- Fix: Wire on-demand revalidation to the CMS publish webhook for commercial pages, and lower
revalidateon pricing, plans and legal pages specifically rather than globally. - Re-test: Publish a trivial change, then fetch the URL and confirm the change appears in raw HTML within the window you defined.
Trap 4: Static export with no regeneration path
A static export, configured with output: 'export', produces the entire site as files at build time and puts no Next.js server in front of them at runtime. That is a legitimate deployment choice and it solves layer two by default, since every page is real HTML. It also means the HTML is frozen at the moment of the last deploy, and anything you fetch client-side after that point never exists in the file at all. DerivateX treats static export sites as a separate diagnostic path, because the ISR checks in Trap 3 do not apply and the header and redirect fixes later in this article have to move to the host or CDN.
- Test: Confirm the mode with
grep -r "output" next.config.*. Then publish a trivial visible change in the CMS without triggering a deploy, wait ten minutes, and runcurl -s https://yoursite.com/pricing | grep -i "your new string". - Expected evidence of failure: Zero matches until a build runs, plus commercial content that appears in the browser but not in the file, plus a sitemap and
lastmodset stamped with the build date. - Fix: Trigger a rebuild and redeploy from the CMS publish webhook for pricing, plans, integrations and comparison pages, and confirm that redirects, canonical headers and any
x-robots-tagrules are configured at the host or CDN, since there is no Next.js middleware layer to run them. - Re-test: A CMS publish produces updated raw HTML at the URL without a manual deploy, within the window you defined, and the deployed file contains every commercial fact with JavaScript disabled.
Trap 5: Metadata declared where Next.js will not read it
Metadata put in the wrong place fails silently. Two variants are common: a metadata export placed inside a client component, and a generateMetadata function that awaits a slow API and falls through to a default, which gives every page in a template the same title. Both are invisible in the browser tab because the client-side router updates it anyway, so the only way to catch either is to read the raw HTML.
- Test:
curl -s https://yoursite.com/integrations/salesforce | grep -i "<title"across a sample of at least 20 template-driven URLs. - Expected evidence of failure: Identical titles across distinct pages, or a title that matches the root layout default.
- Fix: Move metadata to the server component or layout, give
generateMetadataa deterministic data source, and fail loudly in the build rather than defaulting. - Re-test: Every URL in the sample returns a distinct title and description in raw HTML, with no JavaScript executed.
Which canonical and duplicate URL traps does Next.js create?
Routing in Next.js is file-system based with support for advanced patterns and nested layouts, which is convenient to build with and easy to duplicate accidentally. DerivateX finds the same four canonical failures across most B2B SaaS deployments, and they compound: duplicates split signals, and split signals reduce the chance any single URL is the one an engine retrieves.
Trap 6: metadataBase pointing at localhost or the preview domain
When metadataBase is unset or environment-dependent, canonical and Open Graph URLs resolve against the wrong origin. The production page then declares a canonical on a Vercel preview URL, and the Next.js site describes Vercel as a frontend cloud from the creators of Next.js, so those preview hosts are real, reachable and perfectly indexable if nothing stops them.
- Test:
curl -s https://yoursite.com/ | grep -i "rel=\"canonical\""and inspect the absolute URL. - Expected evidence of failure: A canonical containing
localhost,vercel.app, a staging subdomain, or a protocol-relative path. - Fix: Set
metadataBasefrom a single production environment variable, set an explicitalternates.canonicalper route, and returnx-robots-tag: noindexon all non-production hosts at the edge. - Re-test: Canonical resolves to the production origin on every sampled URL, and a preview URL returns the noindex header.
Trap 7: Trailing slashes, casing and locale prefixes producing parallel URLs
Four variants of one page is normal in an untested Next.js build: with and without trailing slash, mixed case, and the default locale served at both / and /en/. Each variant can return 200 with the same content and a self-referencing canonical.
- Test: Request all variants with
curl -sIand record status codes and canonical values for each. - Expected evidence of failure: Two or more variants returning 200 with different canonicals.
- Fix: Choose one form in
trailingSlash, redirect the others with 308, force lowercase at the edge, and make the default locale render at a single path. - Re-test: One variant returns 200, every other returns a single-hop 308 to it, and the canonical matches the surviving URL exactly.
Trap 8: Redirect chains, 307s where 308s belong, and missing conventional URLs
Middleware redirects stack on top of next.config redirects, and you end up with three hops where one would do. Temporary 307s get shipped for permanent moves. Separately, buyers and bots both guess URLs directly, and the developer convention argument that any product charging money should have a /pricing URL matters here: if your pricing lives only as an anchor on the homepage, a direct request for that path returns a 404.
- Test:
curl -sIL https://yoursite.com/pricingand count the hops and status codes in the chain. - Expected evidence of failure: More than one hop, a 307 on a permanent move, or a 404 on the conventional path.
- Fix: Collapse chains to a single 308, move permanent rules out of middleware into config where possible, and make conventional paths resolve, even if they redirect to an anchor.
- Re-test: Every commercial URL reaches its destination in at most one hop, verified with the full header trace.
Trap 9: notFound() and soft 404s returning 200
Deleted case studies, retired integrations and sunset plan pages often keep returning 200 with an empty layout because the data fetch resolved to null and the route never called notFound(). Crawlers keep the URL, retrieval systems keep the stale reference, and your sitemap keeps listing it.
- Test: Request a known-dead URL and a random nonsense path under the same template with
curl -sI. - Expected evidence of failure: Status 200 on both, with near-identical body length.
- Fix: Call
notFound()whenever the data source returns nothing, and 410 genuinely retired commercial URLs rather than 404ing them. - Re-test: Dead paths return 404 or 410 in the header trace, and the sitemap no longer lists them.
Which crawl and discovery traps block bots before rendering even matters?
These three fail earlier than everything above, which makes them the highest-priority checks in the DerivateX sequence. If the crawler never receives your HTML, no amount of server-side rendering helps.
Trap 10: Navigation that produces no crawlable anchor
Programmatic navigation through router.push inside a button handler, mega-menus that render on hover through client state, and paginated resource libraries with a load-more button all move users forward without ever emitting an href. Deep pages then depend entirely on the sitemap for discovery.
- Test:
curl -s https://yoursite.com/ | grep -o 'href="[^"]*"' | sort -u | wc -land compare against the link count in the rendered page. - Expected evidence of failure: A large gap, for example 18 anchors in raw HTML against 90 clickable destinations in the browser.
- Fix: Use the Next.js link component with a real
hreffor every destination, and give paginated lists numbered anchor links alongside the load-more control. - Re-test: Raw HTML anchor count is within a small margin of the rendered count, and every commercial page is reachable within three hops of the homepage using only raw HTML links.
Trap 11: Middleware, CDN and WAF rules blocking named AI crawlers
Next.js exposes a proxy layer that lets you define routing and access rules in code for authentication, experimentation and internationalization. Bot mitigation at the CDN sits above that. Hosts ship this kind of protection by default now: Render, for example, positions itself as the cloud for builders and lists built-in DDoS protection and edge caching powered by its global CDN among its platform features. Useful protection, and also the most common reason a perfectly rendered page never reaches an AI crawler.
- Test: Run the same request four times with different user agent strings: a normal browser, OAI-SearchBot, GPTBot and PerplexityBot. Compare status codes and body length.
- Expected evidence of failure: 403, 429, a challenge redirect, or a 200 whose body is a JavaScript challenge page rather than your content.
- Fix: Allow the crawler user agents you want to be cited by at the WAF, exclude them from geo-redirects and A/B assignment in middleware, and confirm no bot rule caches a 403 at the edge.
- Re-test: All four requests return 200 with comparable body length, and the pass is repeated from an external IP rather than from your office network.
Trap 12: Sitemaps generated at build time that miss the pages you publish most
A sitemap.ts file that reads from a build-time data source will not contain content published after the last deploy, and it often stamps every entry with the build date as lastmod. That makes the file useless as a freshness signal and worse than useless as a discovery mechanism for a resource library that updates weekly.
- Test: Fetch the sitemap, count URLs, and diff that list against your CMS record of published pages.
- Expected evidence of failure: Missing recent URLs, identical
lastmodvalues across hundreds of entries, or dead URLs still listed. - Fix: Generate the sitemap from the live data source with real per-URL modification dates, split it by content type, and exclude anything that returns a non-200 status.
- Re-test: Publish a page, then fetch the sitemap and confirm the URL and an accurate
lastmodappear without a redeploy.
How do I verify the fix in logs or rendered HTML?
Verification means one of three artifacts: a saved HTTP response with headers, a raw HTML body containing the string you care about, or a log line naming the user agent, path and status. DerivateX will not mark a technical fix as closed on a screenshot or a third-party tool summary, because both abstract away the exact thing under test.
| Check | Pass evidence | Fail evidence | Owner |
|---|---|---|---|
| Crawler access to money pages | 200 for browser, OAI-SearchBot, GPTBot and PerplexityBot on the same URL | Any non-200, challenge page, or body shorter than 5KB | Platform or infrastructure |
| Commercial content in raw HTML | Price, plan name and feature rows present with JavaScript disabled | Content appears only after hydration | Frontend engineering |
| Freshness of cached or exported HTML | A CMS publish reaches raw HTML within the defined window | Content changes only on redeploy, or a high cache age with stale prices | Frontend and platform |
| Canonical correctness | Absolute canonical on the production origin, unique per URL | Preview or localhost origin, or duplicate canonicals across variants | Frontend engineering |
| Duplicate URL suppression | One 200 per page, all variants 308 in a single hop | Two or more variants returning 200 | Edge or platform |
| Internal discovery | Every commercial URL reachable in three hops using raw HTML anchors only | Pages reachable only through client-side navigation | Frontend and SEO |
| Log confirmation | Named AI crawler user agents present in 30 days of access logs with 200 responses | User agent absent entirely, or present only with 403 | SEO with platform access |
Log file analysis is the step most software firms skip, and it is the only one that tells you what actually happened rather than what should happen. Filter your CDN or origin access logs by user agent substring, then group by status code and path. If a named AI crawler never appears in 30 days across any path, the problem is access, not content, and no amount of publishing will change the outcome.
Two honest limits on this. Absence from logs can also mean the crawler simply has no reason to fetch you yet, so treat it as a signal to investigate the edge rather than proof of a block. And user agent strings can be spoofed by third parties, so cross-check with reverse DNS or published IP ranges where the operator provides them before you build allow rules around a string alone.
Which bot assumptions are documented, and which are guesses?
Write the distinction down, because engineering will ask and a vague answer stalls the ticket. DerivateX keeps this split explicit on every technical engagement, and the honest version is short.
Documented: Google publishes how Googlebot crawls and renders JavaScript, and which of its own tools reproduce that behavior. OpenAI publishes a publishers and developers FAQ describing its crawler user agents and publisher controls. Those two documents are the ground truth you can point an engineer at.
Not documented, and therefore untested until you test it: whether any given AI retrieval crawler executes your JavaScript, how long it waits for a streamed chunk, whether it follows a 307 the same way it follows a 308, and how it resolves conflicting canonicals. Do not assume all LLM crawlers behave the same way, because they are operated by different companies on different infrastructure with different cost constraints. What you can control is whether the content exists in the first response. That is the whole engineering objective.
The practical rule DerivateX applies: build for the least capable retrieval client you care about being cited by, then let the more capable ones benefit. That is a server-side rendering default for anything commercially decisive, with client-side interactivity layered on top rather than underneath. Our longer write-up on JavaScript rendering failures in AI search covers the framework-agnostic version of this argument.
How does DerivateX run this on client sites, and when should you not hire us?
DerivateX starts every technical engagement with a Citation Surface Map. A Citation Surface Map is an inventory of every URL that could plausibly be retrieved for a buyer prompt in your category, scored on four axes: crawler access, raw HTML completeness, canonical integrity and internal discoverability. It produces a list of URLs with pass or fail against the table above, not a PDF of recommendations.
Only after that map is clean do we move to Citation Engineering, which is the methodology DerivateX uses to make language models recommend a brand deliberately rather than accidentally, through evidence, corroboration and coverage of the questions buyers actually type. Running that work on a site that returns skeleton HTML to retrieval crawlers is spending money on content nobody can fetch. This is also why we track an AI Visibility Score across engines rather than reporting rankings alone: the AI Visibility Score is a measure of how often and how prominently a brand appears in AI answers for a defined prompt set, tracked weekly.
Two results worth citing here. Verito went from an average position of 40 on Google to first page, and is cited and recommended on ChatGPT and Google AI Overviews for 40 of their commercial hosting queries. Gumlet attributes more than 20% of monthly inbound revenue to AI discovery. Both are infrastructure-heavy products where the technical layer was part of the work, not an afterthought.
Now the part most agencies leave out. Everything in this article is free to do yourself. The Next.js documentation, Google’s JavaScript SEO guidance and OpenAI’s publisher FAQ cost nothing, and a competent frontend engineer plus a Head of SEO can run this checklist in a week. If your problem is a platform migration, hire a Next.js engineering shop instead of an SEO and GEO agency, because they will do it faster and cheaper. If what you actually want is a SaaS-focused SEO partner with a long track record in keyword strategy, content production and paid search under one roof, SimpleTiger positions itself exactly that way and is a genuine option worth quoting alongside us; teams whose main gap is organic content volume are often better served there than by a citation-first engagement. If you are below $5M ARR with fewer than 30 indexable pages, the DerivateX floor of $5,000 retainer plus $1,000 to $1,200 off-site budget, so $6,000 to $6,200 all in on our published pricing, will not pay back inside two quarters. Fix the traps yourself and revisit when you have a content surface worth defending.
Where DerivateX is the right call is the situation this article was written for: rankings are healthy, AI visibility is near zero, the CMO has asked what the company is doing about it, and nobody in house has time to run a four-layer diagnostic across 400 URLs and then rebuild the evidence surface behind it. The gap is a missing capability, retrieval-crawler diagnostics plus citation evidence work, and hiring for it in house takes two quarters you may not have. It is also worth saying plainly that the traffic decline is not something better keyword work would have prevented. This work sits alongside our technical SEO service for SaaS and the broader B2B SaaS AI search visibility guide.
Frequently asked questions
How do I test if OAI-SearchBot can access my Next.js site?
Run curl -sI -A "OAI-SearchBot" https://yoursite.com/pricing from an external network. A 200 with a full HTML body passes. A 403, 429, challenge redirect or short body means your CDN, WAF or Next.js middleware is blocking the request, so DerivateX repeats the same test for GPTBot and PerplexityBot.
Why does my Next.js pricing page rank on Google but never appear in ChatGPT?
Usually because Googlebot renders your JavaScript and the retrieval crawler feeding ChatGPT received a shell without the prices. Check raw HTML with curl, not the browser inspector. 28% of ChatGPT-cited pages have zero organic Google visibility, so the two systems reach different conclusions from different inputs. DerivateX tests both paths independently.
Does server-side rendering fix AI crawler visibility on its own?
No, because server-side rendering only solves layer two of four. You still need crawler access at the edge, correct canonicals so the right URL is the one retrieved, and crawlable anchor links so deep pages are discoverable. DerivateX has seen fully server-rendered B2B SaaS sites invisible to AI search purely because of a WAF bot rule.
How much does it cost to fix Next.js technical SEO for a B2B SaaS company?
Fixing the 12 traps yourself costs engineering time, roughly one to two sprints for a site under 500 URLs. A DerivateX diagnostic is $3,500 one time, delivered in two weeks and credited in full against month one if you convert within 30 days. Ongoing engagements start at $6,000 to $6,200 all in per month.
What should engineering check before every Next.js release?
Four automated gates in continuous integration: raw HTML contains the expected commercial strings on key routes, canonical resolves to the production origin, no commercial URL returns more than one redirect hop, and a sample of routes returns 200 to named AI crawler user agents. DerivateX ships these as assertions, not as a manual checklist.
Run the free AI visibility audit on derivatex.agency and DerivateX will return, within 48 hours, which AI engines currently cite you, which competitors they name instead, and which of these 12 access and rendering traps are live on your domain.













