Encited logo
PricingTalk to sales
Headless Magento SEO: What PWA Studio, Alokai and Next.js Storefronts Send to Crawlers

Headless Magento SEO: What PWA Studio, Alokai and Next.js Storefronts Send to Crawlers

Oct 29, 2026October 29, 2026·by Aki from Encited

What a headless Magento storefront sends to Googlebot and AI crawlers, why Hyvä is not headless, where schema breaks on migration, and the pre-rendering fix.

TL;DR. A headless Magento storefront keeps Magento for the catalog and serves pages from a separate app on the GraphQL API, so what a crawler gets depends on that app. PWA Studio and Venia send an application shell and render the product in the browser. Alokai and a custom Next.js or Nuxt front send the product in the HTML and fetch reviews, stock and customer prices later. Hyvä is a Magento theme rendered by Magento's PHP, so it is never headless. Googlebot runs the JavaScript in a second pass; GPTBot, ClaudeBot and PerplexityBot never run it. A pre-render layer in front of the storefront gives every crawler the finished HTML without a rebuild.

Headless Magento SEO comes down to one question per page: is the product in the first HTML response, or does it arrive after JavaScript runs? Magento's admin settings for canonicals, robots tags and metadata still exist on a headless build, and most of them stop having any effect, because the storefront app writes the HTML now. Those settings, and everything else that applies to a Luma or Hyvä store, are in Magento SEO. Everything below is the part that changes when the frontend leaves Magento.

What a headless Magento storefront sends to Googlebot and AI crawlers

On a headless build, Magento answers GraphQL queries and a separate app answers page requests. A crawler never talks to Magento. It talks to the app, and the first HTML response takes one of three shapes.

A shell is a root element, a script tag and little else; the app queries GraphQL in the browser and builds the page, which is how PWA Studio works by default. A rendered first view carries the product name, price, description and images and leaves gaps for the sections that fetch after hydration: reviews, stock, customer-group prices, recommendations. Alokai and most Next.js or Nuxt builds work this way. A complete page has everything a shopper sees on first load, with JSON-LD and head tags; on a headless build that takes deliberate work, and it is what a pre-render snapshot produces.

Googlebot copes with the first two shapes, with a delay. Google's JavaScript SEO basics says Googlebot "queues the page for rendering," and that "The page may stay on this queue for a few seconds, but it can take longer than that." Vercel and MERJ measured that queue on nextjs.org in April 2024 and published the results on July 31, 2024: a median rendering delay of 10 seconds, about 18 hours at the 99th percentile, and "100% of HTML pages resulted in full-page renders". The queue is explained in how Google renders JavaScript.

AI crawlers skip the second pass. The same team's December 17, 2024 piece found that "none of the major AI crawlers currently render JavaScript", naming OAI-SearchBot, ChatGPT-User, GPTBot, ClaudeBot and PerplexityBot. A shell storefront is blank to them, and a rendered first view is a product with no reviews and, on some builds, no price. The crawler-by-crawler detail is in do AI crawlers render JavaScript.

What PWA Studio, Venia and the Edge Delivery storefront send

Adobe's own storefronts ship as shells by default, and Adobe's documentation says so.

PWA Studio's content rendering guide describes Venia's request flow. The UPWARD server "returns a pre-built, server-side rendered HTML response that contains the PWA application shell." After that, "a JavaScript bundle renders the rest of the page content on the client side using React components", and those components "may make additional calls to the UPWARD server to get the data they need to finish rendering." The same guide states the consequence: "When a search engine crawler processes a page, it indexes the initial HTML response from the server." For Venia, that response is the shell: no product name, no price, no description. UPWARD's FileResolver and TemplateResolver can serve real HTML, and both need you to produce it for each product yourself.

Adobe's newer storefront for Edge Delivery Services renders product data in the browser too, and Adobe built a separate project to work around that. The AEM Commerce Prerender README says the tool "fetches products from the Catalog Service, detects changes, and generates HTML" to "Make content accessible for AI agents and parsers that cannot execute JavaScript". Adobe's SSR and crawlability page says SSR "Places the canonical content of a page in the initial HTML response, before any JavaScript runs", then that it "isn't a single setting you enable across an entire catalog."

If the storefront is already live on a shell, the shortest route to a product in the first response is a pre-render layer. Encited's dynamic rendering loads each page in a browser, waits for the product to render, and serves that snapshot to crawler user agents while shoppers keep getting the PWA. Snapshots are stored across 300+ locations and refresh daily, weekly, monthly or quarterly per URL group, and you can re-cache a product by hand after a price change.

What Alokai, Vue Storefront and a custom Next.js or Nuxt front send

A storefront built on a server-rendering framework sends the product in the HTML and leaves holes where the lazy data goes.

Alokai, the company that built Vue Storefront, ships two storefront apps. Its tech stack page lists one "built with Next.js (React) framework" and one "built with Nuxt (Vue) framework", and both render the first view on the server. Alokai's data fetching guide sets the rule: "always use server-side fetching except for" two kinds of data. Lazy loaded data is "data that is not visible immediately on page load and can be removed from server-side rendered html to reduce its size", with product reviews behind an accordion as the example. Personalized data is "data that is different for each user or group", with customer-group pricing, recommendations and the cart as the examples.

That rule is right for shoppers, and it draws the crawler map for you: everything on the lazy list is missing from the HTML a non-rendering crawler reads, and everything on the personalized list is missing or generic. An AI crawler reads a product with no reviews, the guest price on a B2B store, and no stock message.

A custom Next.js or Nuxt front on Magento's GraphQL API behaves the same way, with one more trap: each product section a developer moved into a client component or an effect hook leaves the HTML. View the source of a product page and search for a review line, the stock message and the price; whatever is missing is what GPTBot cannot find. The pitfalls of any single-page app are in SPA SEO, and best ecommerce platform for SEO puts the headless options next to Luma and Hyvä.

Filter URLs drift too. The app decides whether a filter changes the path or a query string, whether the filtered page carries a canonical to the category, and whether the parameter names still match the ones robots.txt blocks. Check the rules in Magento layered navigation SEO against the app's router.

Is Hyvä headless? No, and that is why its rendering is simpler

Hyvä is a Magento theme. Hyvä's getting started guide installs it with composer require hyva-themes/magento2-default-theme and activates it under Content > Design > Configuration, the same place as Luma. Magento's PHP still renders every page. Hyvä swaps Luma's RequireJS, jQuery and Knockout frontend for Tailwind CSS and Alpine.js, and the HTML still comes from PHTML templates.

For a crawler, the difference from PWA Studio is complete. A Hyvä product page arrives as full HTML: name, price, description, images, breadcrumbs and whatever JSON-LD the theme or an extension prints, all before any script runs. A crawler that never runs Alpine.js loses the gallery and the cart, nothing it needs for indexing.

Hyvä PWA Studio and Venia Alokai, Next.js, Nuxt
Who renders the page Magento's PHP templates React in the browser, after a shell from UPWARD The framework on the server, then the browser
Product in the first HTML Yes No Name, description, images, guest price
Reviews in the first HTML Only if the theme prints them No Usually no, they are lazy
Googlebot Indexes on the first pass Waits for the second pass First pass, second pass for the gaps
AI crawlers Full page Blank Product without the lazy and personalized sections
Canonical and robots tags Magento admin settings The app's head component The app's head component

One thing to check on Hyvä: SEO extensions that print JSON-LD, hreflang and meta templates need a Hyvä compatibility module, and which vendors ship one is in Magento 2 SEO extensions. If you have not built yet, Hyvä gets you server-rendered pages with Magento's SEO settings intact and nothing to pre-render.

Where schema, canonicals and robots tags break on a headless migration

A headless migration deletes the head of every page and rebuilds it in the app, and the rebuilt head is where the regressions live.

Luma's microdata is gone, and nothing replaces it. Luma marks products up with schema.org microdata inside its PHTML templates: name, SKU, description, image, an Offer with price and currency, and the rating. Those templates do not run on a headless front. Venia's product component prints no Product markup, and a Next.js or Nuxt product page prints whatever the developer wrote, which on day one is usually nothing. The page drops out of the merchant listing results that Google's product structured data documentation describes. Breadcrumbs go the same way: Luma's breadcrumb block carried BreadcrumbList microdata, and a headless breadcrumb component is a visual element until someone adds the JSON-LD.

The fix is a server-rendered JSON-LD block per page. Print one Product block (a ProductGroup with hasVariant entries for configurables) and one BreadcrumbList block into the server HTML of every product page; the field list and a complete block are in the product schema section of Magento SEO. On a PWA Studio shell there is no server HTML to print into, so the block arrives with the render: Googlebot sees it after the second pass, AI crawlers never do, and Google's warning about Product markup that depends on JavaScript is in JSON-LD injected by JavaScript.

Canonical and robots tags move into the app. Magento's "Use Canonical Link Meta Tag For Products" and "Default Robots" settings write tags into Luma's head. A headless app ignores them and sets its own through a head component: react-helmet in Venia, useHead in Nuxt, the metadata export in the App Router. Three things go wrong.

  1. The tag is set after load, from the GraphQL response, so the raw HTML carries no canonical or the shell's default. Google's JavaScript SEO page says "Google Search will pick up the injected canonical URL when rendering the page", so Googlebot gets it eventually, and the AI crawlers read the shell's version.
  2. The shell ships noindex, as a staging default or a guard for unknown routes, and removes it in the browser. Google's page warns that when Googlebot finds noindex in the raw HTML it "may skip rendering and JavaScript execution". Every product stays out of the index.
  3. Magento's canonical pointed every category-path URL at /trail-runner-2.html. The app's router answers on /products/trail-runner-2 and /running/trail-runner-2 with a self-referencing canonical on each, and the duplicate is back.

Status codes are the app's job. Magento returned a real 404 for a missing URL key; a headless router that renders a not-found component for unknown paths returns 200, a soft 404. Set the status on the server, with notFound() in Next.js or createError({ statusCode: 404 }) in Nuxt, and on a pre-rendered page <meta name="prerender-status-code" content="404"> turns the snapshot into a real 404.

Before you cut over, diff the raw head of one product, one category and one CMS page between Luma and the new front. Every line Luma had and the new front's raw HTML lacks is a regression.

Why AI crawlers log 499s on a storefront that waits on GraphQL

A 499 is Nginx's code for a client that closed the connection before the server answered, and on a headless Magento storefront the AI fetchers are the clients that close it.

The fetcher requests a product page, the storefront app starts its server render and sends GraphQL queries to Magento, and Magento serves them from cache only when the request shape allows it: Adobe's GraphQL caching page says "Only queries submitted with an HTTP GET operation can be cached. POST queries cannot be cached." and "If you specify any uncached query, the system bypasses the cache for all queries in the call." A product query sent as a POST, or batched with a cart or customer query, goes to PHP on every request. The app waits, the fetcher gives up, and Nginx logs the 499. ChatGPT, Claude and Perplexity fetch pages live when they answer, so a page that is slow at that moment is left out of the answer; how to rank in LLMs covers how each engine picks and drops sources.

Googlebot does not log a 499; it slows down. Google's crawl budget documentation says the crawl capacity limit rises when the site responds quickly and falls when it slows down or returns server errors, so a storefront that waits on uncached GraphQL gets fewer product pages crawled per day.

Three fixes, in order: send cacheable queries as GET and keep cart and customer out of the same call as products; put Varnish or Fastly in front of Magento, which the same Adobe page recommends for production; and pre-render, so the fetcher never waits on a render. The awk one-liner that lists the URLs fetchers gave up on is in the 499 section of Magento SEO.

To see the shape your storefront sends before you change anything, run one product page through the crawler simulator as ChatGPT, as Claude and as Googlebot. It requests the URL with each crawler's user agent, two dozen in all, and shows the status code and the raw HTML that came back. Search that HTML for the product name, the price, a line from a review and application/ld+json. The lines that are missing are your migration list.

How to pre-render an existing headless Magento build and verify it

A pre-render layer sits in front of the storefront, checks the user agent, and serves crawlers a snapshot of the rendered page while shoppers get the app.

Google's dynamic rendering page says "Dynamic rendering was a workaround and not a long-term solution" and recommends "server-side rendering, static rendering, or hydration as a solution." It also says "As long as your dynamic rendering produces similar content, Googlebot won't view dynamic rendering as cloaking." For a storefront you are still building, server-render the product. For one that is live on a shell, the snapshot gets every crawler the complete page without a rebuild.

With Encited, setup is a DNS change or a Cloudflare Worker in front of the store, with no Magento module and no change to the storefront app. On a product page that fills in after a GraphQL round trip, set window.prerenderReady = false in the shell and true once the product data is on the page; the renderer waits for the flag before it stores the snapshot. A reviews accordion that only fetches on a click is still absent from the snapshot, so fetch it on load and collapse it with CSS. Plans start at $19 a month, with a 14-day free trial on every account.

Verify in three places.

The crawler simulator

Run the same product page through the Googlebot simulator and the ChatGPT crawler simulator again. The raw HTML should now hold the product name, the price, the review text, the canonical, the BreadcrumbList and the Product JSON-LD.

URL Inspection in Search Console

Open the product URL, choose "Test live URL", then "View tested page". The HTML tab is what Google's renderer received, and with a snapshot in place it is the complete page. A week later, "View crawled page" on the same URL shows the version Google indexed; it should match. Adobe's SSR page says "SEO and SSR changes are best validated in Google Search Console".

The crawl log

Your crawl log lists each bot visit with the URL, the status it got back and whether it was served the snapshot. Filter to GPTBot, ClaudeBot and PerplexityBot and look for every product page at 200 and no 499s.

FAQ

Is headless Magento bad for SEO? Headless is as good as the HTML its first response carries. A PWA Studio shell is indexed late by Google and never read by AI crawlers; a server-rendered Alokai, Next.js or Nuxt front is indexed on the first pass with the lazy sections missing; a pre-rendered storefront sends every crawler the complete page.

Is Hyvä headless? No. Hyvä is a Magento theme that Magento's PHP renders on the server. It replaces Luma's frontend stack with Tailwind CSS and Alpine.js, and Magento's canonical, robots and layered navigation settings keep working on it.

Does Google index PWA Studio and Venia product pages? Yes, after rendering. Adobe's guide says a crawler "indexes the initial HTML response from the server", which for Venia is the application shell, and Google renders the page in a second pass that Vercel and MERJ measured at a median of 10 seconds and about 18 hours at the 99th percentile.

Do Magento's SEO settings still work on a headless storefront? URL rewrites, redirects and the sitemap generator still work because they live in Magento. The canonical, robots, metadata and layered navigation settings write into Luma's templates, which a headless app does not use, so the app has to set those tags itself.

Does pre-rendering a headless Magento store count as cloaking? Google's dynamic rendering page says "As long as your dynamic rendering produces similar content, Googlebot won't view dynamic rendering as cloaking." The snapshot is the page a shopper sees after it renders, with the same product, price and text.

Google
Gemini
ChatGPT
Claude
Grok
Meta
Perplexity

Get your store ranked in AI

See how ChatGPT, Gemini, Claude and Perplexity recommend your products and which stores they pick instead.

Talk to sales
Avatar
How can we help?
Get instant answers to your questions or leave a message for an engineer will reach out
Ask AI about Encited
See our docs
Contact support
Leave a message
We'll get back to you soon
Avatar
Ask AI about Encited
Product questions, troubleshooting and
Thinking
Preview
Drop an image to attach
Powered by ReplyMaven