An item page renders one business-solution item from a parameterized route, such as a product at /product/[handle] or a blog post at /blog/[slug]. Unlike main pages, which get their SEO tags injected automatically with no code, item pages need code to resolve an item's tags.
This article explains how to add the necessary code so SEO tags a Wix user edits in the dashboard appear on your headless frontend. After a one-time layout change to expose a tag slot, you add support in 2 parts:
For how these parts fit together, and how main pages differ, see About SEO Support.
Before starting to code, make sure that you have:
A Wix-managed headless project built with Astro. It should already include the @wix/astro and @wix/astro-pages packages.
@wix/seo and @wix/essentials installed. Use @wix/essentials version 1.0.10 or later:
Server-rendered item-page routes, configured with output: "server". The calls that fetch SEO tags depend on request context, so don't set prerender = true or use getStaticPaths() on these routes.
Expose a named seo-tags slot in the <head> of your shared layout. Item pages fill this slot with the Wix-resolved tags.
Main pages leave it empty and get their tags from automatic SEO injection, so don't add fallback <title> or <meta name="description"> tags here, since they'd duplicate the injected ones.
In each item-page route file, export a wixMetadata object so the page appears in the registry at /_wix/pages.json. Source the app ID, page identifier, and slug token from WIX_APPS rather than hard-coding them:
Caution: Reference WIX_APPS directly inside the wixMetadata object, as shown. Don't read it into a separate variable first, such as const { id } = WIX_APPS.<solution>. Because wixMetadata is a module-level export, Astro evaluates it in the module scope, where variables declared in the component body aren't available.
Important: The key inside identifiers must be the route parameter of your route file, not the token the SDK uses. A route file named [handle].astro uses the key handle; [slug].astro uses slug.
Each SEO-supported page type pairs a page-metadata accessor and slug token from WIX_APPS in @wix/essentials with an item type from seoTags.ItemType in @wix/seo. Because the packages don't reference each other, use this list to pair them for the wixMetadata export in Step 2 and the loadSEOTagsServiceConfig() call in Step 3:
WIX_APPS.checkoutAndOrders.productPageMetadatahandleseoTags.ItemType.STORES_PRODUCTWIX_APPS.checkoutAndOrders.categoryPageMetadatacollectionseoTags.ItemType.STORES_CATEGORYWIX_APPS.bookings.servicePageMetadataslugseoTags.ItemType.BOOKINGS_SERVICEWIX_APPS.events.eventPageMetadataslugseoTags.ItemType.EVENTS_PAGEWIX_APPS.blogs.postPageMetadataslugseoTags.ItemType.BLOG_POSTWIX_APPS.blogs.categoryPageMetadataslugseoTags.ItemType.BLOG_CATEGORYThe WIX_APPS type is the source of truth for the exact accessor strings, the pageIdentifier each one resolves to, and the current set of supported page types. This list exists to pair each type with its seoTags.ItemType.
Note: For Stores, the accessor is WIX_APPS.checkoutAndOrders, not WIX_APPS.stores. WIX_APPS.stores.id is the catalog ID used for catalogReference.appId in cart operations.
In the route's frontmatter, import the SEO helpers and call loadSEOTagsServiceConfig() at request time, then render the result into the seo-tags slot. The function takes the following fields:
pageUrl: The canonical URL of the current page. Use Astro.url.href.itemType: A seoTags.ItemType value that identifies the page type.itemData: The item's identifier, always as { slug: "<value>" }. The key is literally slug for every page type; the value is your route parameter, such as Astro.params.handle on a [handle].astro route.Tip: When the page also fetches item data, run loadSEOTagsServiceConfig() in parallel with that fetch using Promise.all to avoid an extra round trip. See the example below.
loadSEOTagsServiceConfig() returns the tags Wix has already resolved for the item. A Wix user controls these values in the dashboard under SEO & GEO > SEO Settings, where each page type, such as Blog posts, has its own settings, and individual items can override them in their own SEO fields. You don't set these values in code. Your code only renders the resolved result.
<SEO.Tags> covers the tags managed in the dashboard. To add schema.org structured data for rich results, render a JSON-LD script from the item you already fetched, using the schema type that fits the page, such as Product for store products or Event for events:
<SEO.Tags> resolves the tags once, on the server. If your frontend navigates between items on the client without a full page reload, such as moving from one product to another in a single-page flow, the <head> keeps the first item's tags unless you update them.
To re-resolve tags on the client, wrap the relevant content in <SEO.Root>, which provides the SEO service context, and use <SEO.UpdateTagsTrigger>, a render-prop component that exposes an updateSeoTags function. Call it with the new item's type and slug to swap the tags in place:
<SEO.UpdateTagsTrigger> must be nested inside <SEO.Root>. For a server-rendered page that emits its tags once, <SEO.Tags> alone is enough; you don't need <SEO.Root>.
To verify that your code is bringing the SEO tags as expected:
Run the build command to build the assets for your project:
Run the release command to publish your project:
Fetch a real item URL that returns 200, not 404, and inspect the resolved <title> in its <head>:
Confirm the title is that item's real title from the dashboard, not a generic page-type fallback such as Post | <site name>. An unregistered or mistyped route still returns fallback tags, so matching the real item's values is what proves your <SEO.Tags> render resolved the item.
Open /_wix/pages.json and confirm your item-page routes are listed. If the list shows only your static pages, the route isn't registered, so recheck the wixMetadata export. This registry is what feeds the sitemap and the dashboard SEO editor.
Change a value in the dashboard for the relevant page type, and then fetch the page again to confirm the change reaches the live <head>.
Notes:
/_wix/pages.json but missing <SEO.Tags> still ships your layout's default tags.<SEO.Tags> but exports no wixMetadata serves correct tags yet stays invisible to the sitemap.Complete examples for each item-page type. Each uses the values from Values for each page type, sources them from WIX_APPS, and runs the item fetch in parallel with loadSEOTagsServiceConfig(). The fetch helpers, such as getProduct, and the <Layout> component stand in for your own code.
Route file: src/pages/product/[handle].astro.
Register the route:
Fetch and render the tags:
Route file: src/pages/search/[collection].astro. Category pages resolve tags from the route parameter alone, so there's no item to fetch.
Register the route:
Fetch and render the tags:
Route file: src/pages/bookings/[slug].astro.
Register the route:
Fetch and render the tags:
Route file: src/pages/events/[slug].astro.
Register the route:
Fetch and render the tags:
Route file: src/pages/blog/[slug].astro.
Register the route:
Fetch and render the tags:
Route file: src/pages/blog-category/[slug].astro. Category pages resolve tags from the route parameter alone, so there's no item to fetch.
Register the route:
Fetch and render the tags:
/_wix/pages.json is emptyA route file is throwing when Wix imports it, and one failing route is enough to clear the whole list. The usual cause is reading WIX_APPS into a variable instead of referencing it directly inside the wixMetadata export (see Step 2). Fix the export, then rebuild and redeploy.
The route's wixMetadata is missing, or the key in identifiers doesn't match the route's filename parameter: use handle for [handle].astro and slug for [slug].astro. Confirm the route and its identifiers key in /_wix/pages.json.
loadSEOTagsServiceConfig() didn't resolve the item, so only the fallback tags render. Check that the URL returns 200 rather than 404, that itemType matches the page type, and that itemData.slug is the item's real slug.
Server-rendered tags resolve once. To re-resolve them on client navigation, wrap the content in <SEO.Root> and call updateSeoTags with <SEO.UpdateTagsTrigger>, as shown in Step 5.
Last updated: 1 July 2026