Skip to main content
The @frontic/nuxt module wires your Nuxt application to the Frontic backend. It ships composables for data fetching, automatic proxy configuration, and locale management.

Installation

This installs the module and adds it to your nuxt.config.ts automatically.
nuxt.config.ts
The module works out of the box with sensible defaults: CORS proxy enabled, all composables registered, and TypeScript paths configured.

Requirements

The module needs Nuxt 4 or later and Node.js 20.19 or later. Its composables cache through Pinia Colada, so the module brings its own Pinia and registers @pinia/nuxt and @pinia/colada-nuxt unless your app already does. If your app defines its own Pinia stores, they run on the module’s Pinia version - check Pinia’s release notes when upgrading the module, and don’t pin an older Pinia in your own package.json.

Configuration

All options are optional. Customize behavior as needed:
nuxt.config.ts

Options

API

boolean | string
default:"true"
Enable the built-in CORS proxy at /api/frontic, or set a custom path.
number
default:"10000"
Milliseconds before the proxy gives up on the Fetch API. Set 0 to disable the timeout.Without a bound, a stalled upstream holds the request until Node’s default of 300 seconds - long after the page is lost, and long enough to exhaust your server’s connection pool. A timeout returns 504 Gateway Timeout and fires frontic:fetch:error.
string
The secret for a project whose Fetch API is protected. Server-only - kept in private runtime config, never exposed to the browser.

Routing

boolean
default:"true"
Automatically redirect on 301 responses in useFronticPage.
boolean
default:"true"
Throw a 404 error when page is not found in useFronticPage.
boolean | { domains?: string[] }
default:"false"
Serve the project’s sitemap from your app at /sitemap.xml, with domains as the served domains. See Sitemap.

Context

boolean
default:"false"
Disable automatic context fetching and cookie management in useFronticContext.
Cookie name for storing the context token.
Cookie max age in seconds. Default is 1 year.
string | boolean
The value every composable sends as contextDomain, which Frontic matches against your domain rows to resolve scope, region, and locale. It also forms the host portion of the slug useFronticPage looks up.When not set, useFronticPage derives the domain from the request URL host (useRequestURL().host). This works in production where the host is the Frontic domain, but not when the host doesn’t match - for example in local development (localhost:3000), sandbox / preview environments, or staging where only the production domain is configured in Frontic.You can configure a domain alias in your admin app as an alternative. If your dev environment uses a host that isn’t configured as an alias, set contextDomain to override it:
Per-locale domains
A multi-locale storefront needs a different contextDomain per locale, because each of your domain rows binds one scope / region / locale triple. Setting contextDomain: true tells the module to read the domain off the locale that @nuxtjs/i18n currently has active, so it follows locale switches without any per-call wiring. contextDomain is not an i18n feature - it’s a custom property you add to each locale object, which the module then reads through i18n’s localeProperties:
Each value must match a domain row in Settings → Domains. With the rows above, a visitor on the de locale resolves to the German region and locale, and useFronticPage looks up demo-shop.com/de/produkte/schuhe for the path /produkte/schuhe.
contextDomain: true without @nuxtjs/i18n installed, or a locale object with no contextDomain property, logs a warning at build or request time and sends no domain - Frontic then falls back to the project’s default scope, region, and locale rather than erroring. Watch for that warning if a locale silently serves the wrong market.
Resolution order
Every composable resolves the domain the same way, taking the first that applies:
  1. The contextDomain passed in the composable’s own options - per call, and reactive.
  2. The module’s contextDomain string.
  3. The active i18n locale’s contextDomain, when the module option is true.
  4. Nothing. useFronticPage alone falls back once more, to the request host.
useFronticClient additionally accepts contextDomain: false to send no domain at all, ignoring the module config:
The resolved value travels as the fs-domain header and is part of every cache key - see Caching.
The context token
A domain resolves to one scope, region and locale - the one you configured. The context token is what a visitor changes when they pick a different region or locale through useFronticContext, and Frontic gives it precedence over the domain. You do not pass it per call. Every composable reads the token from the context cookie and sends it as fs-context, so a switch reaches the next request on its own:
The token is part of every cache key, so the switch moves each active query onto a new entry and its content is fetched rather than reused. Two ways out, per call or per project:
  • contextKey: false on a single call sends no token, so it resolves through the domain alone. Reach for it when the response goes behind a shared cache, where one visitor’s context must not decide what the next one sees.
  • disableContext: true stops the module managing the token at all - nothing is read, nothing is sent, and you pass contextKey yourself.
useFronticContext({ cookieName }) overrides the cookie per instance. The composables resolve their token from the first scope registered that way, since queries have already keyed on it by the time a second one could move it; a second call naming a different cookie warns in development. Pass contextKey explicitly for that second context.

Frontic UI

The module configures Frontic UI - a set of ready-to-use, customizable components designed for Frontic storefronts. When enabled, the module auto-imports your Frontic UI components so they’re available everywhere in your app without manual imports, mirroring Nuxt’s built-in component auto-import.
Frontic UI is currently in open beta. We’d love to hear your feedback! If you have questions or suggestions, please get in touch with us.
string
default:""
Prefix for auto-imported Frontic UI components from your component directory. For example, setting 'Ui' would make Button.vue available as <UiButton />.
string
default:"@/components/ui"
Directory path for Frontic UI components to auto-import.

Composables

boolean | FronticComposable[]
default:"true"
Control which composables are auto-imported during the Nuxt build.
When a composable is disabled, it will not be auto-imported during the Nuxt build. You won’t get type errors or warnings - the composable won’t exist. Use this to reduce bundle size by excluding composables you don’t need.

Composables

The Frontic composables provide a smart data layer for your Nuxt application. Built on Pinia Colada, they handle caching, deduplication, and SSR hydration automatically - instant UI updates via stale-while-revalidate, shared requests across components, and server-to-client data transfer without extra configuration.

useFronticBlock

Fetch single blocks (products, categories) with smart caching

useFronticListing

Fetch listings with automatic caching and SSR support

useFronticSearch

Full-featured search with filters, sorting, and smart state management

useFronticTree

Hierarchical menu trees with automatic caching and SSR support

useFronticPage

Dynamic page routing with redirects and 404 handling

useFronticContext

Locale and region switching with cookie persistence

useFronticClient

Low-level client for direct API access

useFronticBlock

Fetch a single block (product, category, brand) by key with automatic caching and SSR support. See Caching for details.

Parameters

BlockName
required
The name of the block to fetch. Provides full autocomplete for your generated block types.
string | Ref<string>
required
The key identifier for the block. Can be reactive for dynamic fetching.
object
Configuration options.

Returns

Ref<ResponseOf<T> | undefined>
The block data, fully typed based on the block name.
Ref<'pending' | 'error' | 'success'>
Current query status for loading states.
Ref<'idle' | 'loading'>
What the network is doing, independent of what the data is. Together with status this separates a first load (pending) from a background refresh (success while asyncStatus is 'loading') - the state keepPreviousData produces.
Ref<boolean>
Shorthand for asyncStatus === 'loading'.
ShallowRef<Error | null>
The error thrown by the last fetch - null while pending or after success. See Error handling.
() => Promise<void>
Refresh data using cache if still valid.
() => Promise<void>
Force a fresh fetch, bypassing cache entirely.

Example


useFronticListing

Fetch a listing by name and parameters with automatic caching and SSR support. Mirrors client.listing(name, params, { query }) - including the optional query for filter, sort, search, limit, and page. See Caching for details.
Building a page with interactive filter/sort/search controls or pagination bound to user input? Use useFronticSearch. useFronticListing is the right pick when the query is fixed at call time - homepage carousels with { limit: 10 }, server-rendered grids with a static filter, etc.

Parameters

ListingName
required
The name of the listing to fetch.
ListingParamsOf<T> | Ref
Parameters to pass to the listing endpoint. Type-safe based on listing name.Optional when every parameter of the listing is optional - useFronticListing("ProductSearch") needs no argument, while a listing with a required parameter such as parentKey still does. Options stay the third argument, so pass {} here when you set options on a listing without parameters.
object
Configuration options.

Returns

Ref<ResponseOf<T> | undefined>
The listing data with items and metadata.
Ref<'pending' | 'error' | 'success'>
Current query status.
Ref<'idle' | 'loading'>
What the network is doing, independent of what the data is. Together with status this separates a first load (pending) from a background refresh (success while asyncStatus is 'loading') - the state keepPreviousData produces.
Ref<boolean>
Shorthand for asyncStatus === 'loading'.
ShallowRef<Error | null>
The error thrown by the last fetch - null while pending or after success. See Error handling.
() => Promise<void>
Refresh using cache if valid.
() => Promise<void>
Force fresh fetch.

Example


useFronticSearch

A ready-to-use backend for building stateful search and filter UIs. This composable handles all the interaction logic - filtering, sorting, pagination, and text search - with automatic caching and SSR support, so you can focus on crafting the perfect UI. See Caching for details. It follows best practices to reduce logic overhead in your templates, provides pre-processed filter and sort options with labels and counts, and seamlessly integrates with Frontic UI components.

Parameters

ListingName
required
The name of the listing to fetch.
ListingParamsOf<T> | Ref
Parameters to pass to the listing endpoint. Optional when every parameter of the listing is optional - see useFronticListing.
object
Configuration options.

Returns

State & Cache Control
ShallowRef<ResponseOf<T> | undefined>
The full listing response from the API, containing items, pagination metadata, and filter facets.
Ref<'pending' | 'error' | 'success'>
Current query status. Use this to show loading spinners or error states in your UI.
Ref<'idle' | 'loading'>
What the network is doing, independent of what the data is. Together with status this separates a first load (pending) from a background refresh (success while asyncStatus is 'loading') - the state keepPreviousData produces.
Ref<boolean>
Shorthand for asyncStatus === 'loading'.
ShallowRef<Error | null>
The error thrown by the last fetch - null while pending or after success. See Error handling.
Ref<SearchState<TFilters, TSorts>>
Pre-processed search state ready for building your UI. Fully typed based on your listing’s filter and sort schema - with IDE autocomplete for filter keys, sort fields, and more. Contains everything needed to render filter sidebars, sort dropdowns, and pagination controls.
Ref<string>
Two-way bindable search term. Connect this directly to your search input - typing automatically triggers debounced API requests when the term exceeds searchTermThreshold.
() => Promise<void>
Re-fetch data, using cache if still valid within staleTime. Use this when you want to ensure fresh data but don’t need to bypass the cache.
() => Promise<void>
Force a fresh fetch, completely bypassing the cache. Use this when you know data has changed and need guaranteed fresh results.
() => Promise<void>
Reset all state (search term, filters, and sorting) to initial values and refresh results. Equivalent to calling resetSearch(), resetFilter(), and resetSorting() together. Use this for a “Clear All” button.
Filter Actions
(field, value) => Promise<void>
Add a single filter value while keeping existing selections. Use this for checkbox-style filters where users can select multiple options.
A { from, to } value is recognised by shape and routed to the range filter list. Ranges do not accumulate: a field holds one range, and a second call replaces it.
(field, value?) => Promise<void>
Remove a specific filter value, or clear all values for a field if no value is provided. Use this when users uncheck options or click “clear” on a filter group.
The field’s range is always cleared, whichever value is passed - a range has no single value to remove.
(field, values) => Promise<void>
Replace all values for a filter field at once. Use this for single-select filters or when setting multiple values programmatically.
When the first entry is a { from, to } object the call is treated as a range and the remaining entries are ignored.
(field?) => Promise<void>
Clear filters for a specific field, or all filters if no field is provided. Use this for “Reset” buttons.
Sort Actions
(sortBy?: SortStringOf<TSorts>) => Promise<void>
Apply a sort order using the 'field:order' format. Call without arguments to reset to default sorting.
() => Promise<void>
Reset to the backend’s default sort order. Equivalent to calling sortResult() without arguments.
Pagination Actions
Filtering, sorting, or searching for a new term returns you to page 1 - or to initialPage with infinite: true. A pagination component bound to state.active.page follows this on its own; one holding its own page number will drift out of step with the results.
() => Promise<ResponseOf<T> | undefined>
Load the next page. In standard mode (the default), the current page is replaced with the next one. In infinite mode (infinite: true), items from the next page are appended to the existing list - use this for “Load More” buttons or infinite scroll.
() => Promise<ResponseOf<T> | undefined>
Load the previous page. In standard mode, the current page is replaced with the previous one. In infinite mode, items from the previous page are prepended to the existing list.
(page: number) => Promise<ResponseOf<T> | undefined>
Jump to a specific page number. In standard mode, the current page is replaced with the requested page. In infinite mode, all loaded pages are reset and the requested page becomes the new starting point.
Search Actions
Clear the search term and refresh results. Use this for a “clear search” button.

Example

Why use a wrapper composable?
  1. Centralized configuration - All your filter options, sort labels, and search settings live in one file. Need to add a new filter? Update it once.
  2. Shared state - The cacheKey option enables automatic state sharing. When multiple components call the same wrapper, they share the reactive searchTerm, filter/sort state, result data, and pagination.
  3. Clean components - Your page and filter components stay focused on rendering, not configuration.
Any action (like addFilter) called from one component automatically updates all other components using the same cacheKey.

Restoring state from the URL

A search page usually gets its term from the request - ?name=atomic - not from someone typing. Pass it as initialSearch rather than assigning searchTerm:
app/pages/search.vue
Assigning searchTerm does not work for a term that arrives with the request. It commits through a debounced watcher, which never fires before the server render finishes - and after hydration the value has not changed, so the watcher never runs. The page renders unfiltered results and stays that way. searchDebounce: 0 does not help.
The initial* options commit before the first query, so the server renders the right results and the browser does not refetch to correct them. They apply only when the state is empty, so a value restored from the SSR payload - or set by another component sharing the same cacheKey - wins over them. searchTerm remains the right binding for an input the user types into.

useFronticTree

Fetch a Menu Tree - a hierarchical collection of records assembled from a Data Storage and rendered through a Detail Block - with automatic caching and SSR support. See Caching for details.

Parameters

TreeName
required
The name of the tree to fetch. Provides full autocomplete for your generated tree types.
object
Configuration options.

Returns

Ref<ResponseOf<T> | undefined>
The full tree response ({ items?: [...] }), fully typed based on the tree name.
ComputedRef<TreeItems<ResponseOf<T>> | undefined>
Shortcut for tree.value?.items. Fully typed based on the tree name - when your generated client is in place, items resolves to the concrete node array, so iterating gives you autocomplete on $items, key, and the block fields defined by the tree’s Detail Block (e.g. name, link).
Ref<'pending' | 'error' | 'success'>
Current query status for loading states.
Ref<'idle' | 'loading'>
What the network is doing, independent of what the data is. Together with status this separates a first load (pending) from a background refresh (success while asyncStatus is 'loading') - the state keepPreviousData produces.
Ref<boolean>
Shorthand for asyncStatus === 'loading'.
ShallowRef<Error | null>
The error thrown by the last fetch - null while pending or after success. See Error handling.
() => Promise<void>
Refresh data using cache if still valid.
() => Promise<void>
Force a fresh fetch, bypassing cache entirely.

Example

The cache key includes key, depth, and contextKey, so different subtrees, depth caps, and contexts each cache independently - calling useFronticTree('CategoryNavigation') and useFronticTree('CategoryNavigation', { key: 'shop' }) from different components will not collide.

useFronticPage

Dynamic page routing with automatic slug detection, redirect handling, and 404 errors. Includes automatic caching and SSR support. See Caching for details.

Parameters

string | Ref<string>
The page slug. If omitted, auto-detected from current URL.
object
Configuration options.

Returns

Ref<Page | undefined>
The full page response. Page is your project’s union of page types, discriminated by type - see Typing page data.
ComputedRef<PageData | undefined>
The page data payload for rendering - the union of every page type’s data.
ComputedRef<Page['type'] | undefined>
The page type for conditional rendering. Typed as your project’s page types ('ProductCategory' | 'ProductDetail' | …), not a plain string.
ComputedRef<Page['block'] | undefined>
The block name to render for this page.
ComputedRef<RouteMeta | undefined>
Route information including redirect and context data.
ComputedRef<AlternateRoute[] | undefined>
Alternate language URLs for SEO hreflang tags.
ComputedRef<AlternateRoute | undefined>
Suggested route when context changes (e.g., for locale switching).
Ref<'pending' | 'error' | 'success'>
Current query status.
Ref<'idle' | 'loading'>
What the network is doing, independent of what the data is. Together with status this separates a first load (pending) from a background refresh (success while asyncStatus is 'loading') - the state keepPreviousData produces.
Ref<boolean>
Shorthand for asyncStatus === 'loading'.
ShallowRef<Error | null>
The error thrown by the last fetch - null while pending or after success. See Error handling.
() => Promise<void>
Refresh using cache if valid.
() => Promise<void>
Force fresh fetch.

Example

pages/[...slug].vue

Typing page data

Page is the union of your project’s page types, generated by the CLI. Narrow on page.value.type and page.value.data gets the shape of that page type - no cast needed:
pages/[...slug].vue
Narrow on page, not on the destructured refs. type and data are separate computed refs, so checking type.value does not narrow data.value.
The composable automatically constructs the page slug from the current request URL (host + pathname). This works correctly in both SSR and client-side navigation.

useFronticContext

Manage locale and region switching with cookie persistence. Every call site shares one context. contexts, current, token and isLoading are app-wide state, so a header switcher and a footer switcher always agree, and the context is fetched once per app rather than once per component. A call from a plugin, outside any component, fetches too. Each change fires the context hooks, so code outside components can follow it without holding a ref.

Parameters

object
Configuration options.

Returns

Readonly<Ref<ContextOption[]>>
Available context options with regions and locales.
Readonly<Ref<Context | null>>
Current active context.
Readonly<Ref<string | null>>
The current context token.
(context: { region: string; locale: string }) => Promise<void>
Switch to a different region/locale combination. When the switch hands back a new token, it is written to token and the cookie, so later requests carry it.
Readonly<Ref<boolean>>
Whether the context itself is resolving. Content that depends on it refetches on its own - the token is part of every query key, so a switch re-keys each query - and every composable reports that through its own asyncStatus.
() => Promise<void>
Fetch the available contexts and the current context again. A call while a fetch is already running joins it instead of starting a second one.

Example


useFronticClient

Low-level client for direct API access. All other composables use this internally.

Parameters

object
Configuration options for client behavior.

Returns

Returns a FronticClient instance with type-safe methods:
<T>(name, key, config?) => Promise<ResponseOf<T>>
Fetch a block by name and key.
<T>(name, params, config?) => Promise<ResponseOf<T>>
Fetch a listing with parameters and query options.
<T>(name, config?) => Promise<ResponseOf<T>>
Fetch a menu tree by name. Pass key and/or depth under config.query to fetch a subtree or cap the depth.
(slug, config?) => Promise<Page>
Fetch a page by its slug.
(token, config?) => Promise<Context>
Get context by token.
(token?, config?) => Promise<[ContextOption[], string]>
Get available contexts.
(context, token, config?) => Promise<Context>
Update context with new region/locale.
(domain, index?, config?) => Promise<string>
Fetch the sitemap XML for a Frontic domain. See Sitemap.

Example


Server routes

useFronticClient is a Nuxt app composable - it reads useNuxtApp, useRequestURL and the app’s runtime config, none of which exist in Nitro. A server/ route uses createFronticServerClient(event) instead:
server/api/stock.get.ts
It returns the same FronticClient the composables use, and resolves from the incoming request what the app-side client resolves from the Nuxt app: Failures are announced on the Nitro frontic:fetch:error hook, with the payload the app-side hook carries - see Error reporting.

Parameters

H3Event
required
The request being handled. Its cookies and URL are what the client resolves from.
There is no cache. This is a client, not a query: nothing is deduplicated, nothing is stored, and nothing reaches the SSR payload. For data a page renders, use the composables; for a route’s own response, cache it with Nitro’s own tools.
object
Overrides for what the event would otherwise decide.
contextDomain: true reads the active @nuxtjs/i18n locale, and there is no i18n instance in Nitro. A project configured that way resolves no domain here rather than guessing one - pass contextDomain per call, or address pages by a slug that already carries the domain.

Hooks

The module fires Nuxt hooks as it works, so you can instrument a storefront without wiring anything per call site. Register them in a plugin.
app/plugins/frontic.ts
Every hook name and payload is typed, so payload.trigger autocompletes and a misspelled hook name is a compile error. The listing and search hooks are typed only while useFronticSearch is enabled, and the page hook only while useFronticPage is - see the composables option. To move a handler out of the plugin, type it with the payload types from @frontic/nuxt/types:
app/utils/track-listing.ts
The analytics payloads are FronticListingResolved, FronticSearchCommitted and FronticPageResolved, with FronticAnalyticsItem and FronticListingTrigger for their parts. The context hooks pass a FronticContext. Every analytics payload carries a version, currently 1. It changes only when a payload’s shape breaks - added fields don’t bump it - so a handler can check it before reading fields. Give switch (payload.trigger) a default branch, so a trigger added later doesn’t fall through silently. frontic:listing:resolved fires from useFronticSearch and carries a trigger telling you why the listing changed. The trigger comes from the method you called, never from the data: That distinction is only available inside the module. From outside you can see that data changed, but not whether a shopper applied a filter or a cache revalidated - so listing analytics built on reactive state count both. Cache revalidations never fire the hook, and one shopper action produces one event however many components read the same listing. initial fires once per cacheKey per page view - navigating back to a listing counts as a new view. Each event describes the page that just resolved: page is its number, resultCount its item count, and items its rows. totalCount is the overall total. index is each item’s absolute position in the full result set - the first item on page 2 of a 24-item page is 24, not 0:
app/plugins/frontic.ts
Prices in data are integers in minor units - see Price. This holds with infinite: true as well: loadNext and loadPrev each report the page they fetched, not the accumulated list, and a loadNext past the last page emits nothing. Adding the events up therefore gives exactly what the shopper saw, which is what an impression tracker needs. frontic:search:committed fires only once a term stops changing. Instrumenting off the search input directly records s, sk, ski, skis as four searches, which turns your top-queries report into a list of prefixes:
app/pages/search.vue
It carries term (trimmed), cacheKey, resultCount, page and version. The term counts once it is at least searchMinChars long, whether or not it reached searchTermThreshold and triggered a request.
These knobs are separate from searchDebounce, which controls how fast results update. The two answer different questions and should not share a value.

Page

frontic:page:resolved fires when useFronticPage resolves to an entity. Redirects and 404s don’t fire it. The payload is { page, version }, where page is your project’s Page union - narrow on page.type to read its data, as in Typing page data:
app/plugins/frontic.ts
It fires once per page per page view, so several components reading the same page produce one event.

Context changes

frontic:context:ready fires once, when the context first resolves. frontic:context:updated fires at the same moment and again on every change after it, so a single updated listener sees every context the visitor has. A refresh that resolves to the same region, locale and scope is not announced again, and a listener registered after the context resolved still receives it.
app/plugins/analytics.ts
The payload carries region, locale, scope and token. Its type is FronticContext from @frontic/nuxt/types.

Error reporting

frontic:fetch:error fires wherever a Frontic call fails - composables, useFronticClient, createFronticServerClient and the proxy route.
This hook fires on two runtimes. Composable and SSR calls run in the Nuxt app; the proxy route and createFronticServerClient run in Nitro. To catch every failure, register in both.
The chain is there because connection failures arrive as a bare Bad Gateway, with the real reason in cause:
cause is non-enumerable, so it never appears in console.error or in anything that serialises the error - without the chain, every upstream failure looks identical. The payload type is FronticFetchErrorPayload from @frontic/nuxt/types. Reporting never changes control flow. A failed composable or client call still rejects with the original error, so you can never mistake a failure for an empty result. The proxy answers the browser with its own 502, 504 or 400 error, carrying the original as cause. useFronticContext logs a failed context call instead of rejecting.
The listing, search and page hooks are client-only, so you never double-count on hydration. frontic:fetch:error fires during SSR too - a request that dies while rendering is exactly the failure worth reporting.

Error handling

A composable that fails sets its error ref and status: 'error'. The Fetch API’s error body is on the error’s data. Use the auto-imported isApiError to check it before reading its fields:
isApiError(value) returns true when value has both code and correlationId - a deliberate error, such as a missing record or a failed validation. An unexpected server error (500) carries no code, so it returns false. error is typed as a plain Error, which is why the example casts before reading data. The body types come from @frontic/nuxt/types: details is unknown on purpose: its shape varies by error code. Branch on code, never on error - the message is meant for humans and its wording can change. isApiError is auto-imported whenever at least one composable is enabled, whichever ones they are.

Proxy

The module includes a built-in proxy to prevent CORS issues on client-side requests.
1

Browser Request

Client sends request to your server at /api/frontic
2

Server Forwards

Your Nuxt server forwards the request to the Fetch API
3

Response Returns

Response flows back through your server to the browser
Server-side requests (SSR) go directly to the Fetch API without using the proxy. They carry fs-secret too when you set fetchApiSecret, so every path is authenticated.

What the proxy forwards

The proxy passes your request headers through to the Fetch API, minus three it strips first: Every other header is forwarded unchanged, including fs-context, fs-version, fs-domain and fs-request-url. fs-secret is injected server-side when you set fetchApiSecret. A request without fs-target-url gets 400 Bad Request.
If you run your own proxy route instead of the module’s, strip cookie and authorization yourself. Header forwarding keeps everything except a handful of hop-by-hop names, so a browser-side call otherwise hands your session cookies to the Fetch API.

Timeouts

Calls that go through the proxy are bounded by proxyTimeout, 10 seconds by default. Server-rendered requests reach the Fetch API directly and are not covered by it:
nuxt.config.ts

Sitemap

Frontic generates an XML sitemap for every domain of your project from the pages marked Visible in Sitemap. With sitemap: true, the module serves it from your Nuxt app:
Requests to /sitemap.xml - and /sitemap1.xml, /sitemap2.xml, … when the sitemap is split - are answered with the sitemap for the matching domain, on every locale prefix. https://demo-shop.com/sitemap.xml and https://demo-shop.com/uk/sitemap.xml each serve their domain’s sitemap, ready to submit to search engines or reference from your robots.txt:
robots.txt
The route resolves the domain from the incoming host and path prefix - the same way URLs resolve - injects your fetchApiSecret server-side, and forwards the sitemap’s ETag so crawlers revalidate instead of re-downloading. Responses are cached for ten minutes with stale-while-revalidate. When your config declares the app’s domains (sitemap: { domains: [...] }, a contextDomain string, or contextDomain: true with i18n locales carrying contextDomain properties), the route serves only those domains and answers 404 for any other host. With no declared domains the route serves whatever well-formed host the request carries, and the module warns at build time - declare your domains on any deployment whose forwarded host header is client-controlled.
With @nuxtjs/robots installed, the module adds the Sitemap: directive to your robots.txt automatically. With @nuxtjs/sitemap installed, sitemap: true is ignored with a warning - both would claim /sitemap.xml. An integration that feeds Frontic pages into @nuxtjs/sitemap is planned.
To assemble your own sitemap instead - for example to merge Frontic pages into a larger set - leave the option off and fetch the XML through client.sitemap:

TypeScript

Composable types come from your generated client. Run frontic generate and block, listing and tree names, listing parameters, filter and sort fields, and response types all resolve to your project’s schema - with IDE autocomplete, and a type error for a name or field that doesn’t exist. Until you generate, name arguments ask you to run the generator. Response types such as ProductCard are declared globally by the generated client, so you use them without an import:
Helper types come from the module:
@frontic/nuxt/types is a types-only entry - use import type. It also exports the hook payloads and the error body types, plus Page, PageData, RouteMeta, Context and ContextOption. You import those by name, so they shadow a global of the same name - a project with a Data Type called Page keeps working. The generated types declare your project’s own types as globals, so the framework shapes there sit under the Frontic namespace to stay out of their way; see Types. The module maps @frontic/stack/* to .frontic/* in your project root, where the CLI writes the runtime client. Use the alias to import the client itself:
The module reads its types from a registry that the CLI fills in since generator schema version 2. With an older .frontic/ folder, no composable name type-checks, and the module warns at build time that your client is outdated. Run frontic generate with a current CLI to fix it.

Caching

All composables use Pinia Colada for intelligent caching:

Stale-While-Revalidate

Shows cached data immediately while fetching fresh data in the background

Automatic Deduplication

Multiple components requesting the same data share a single request

SSR Hydration

Data fetched on server transfers to client without duplicate requests

What a Cache Entry Is Keyed By

requestUrl is deliberately not in the key: it is per-route request metadata, not content identity, and keying on it would split the cache into one entry per visited URL. If you are putting these responses behind a server cache of your own, pass requestUrl: false so the outgoing request stops varying per visitor too - and contextKey: false alongside it, for the same reason. Alongside the composable’s own arguments, every cache key carries the resolved context domain and the active context token. Two locales resolving to different domains therefore never share an entry, and switching context moves every active query onto a new entry rather than reusing the old one - which is what fetches the new context’s content. This matters most when one process serves several domains - prerendering a multi-locale site, warming a cache, or rendering multiple tenants - where a domain-blind key would hand the second request the first one’s payload.

Configure Cache Duration

staleTime decides how long fetched data is trusted; gcTime decides how long an entry nobody is reading is kept before it is dropped.

Data on Screen While a Key Changes

A cache entry is keyed by everything the request depends on, so changing a filter, a page or a context is a different entry - with no data of its own yet. By default the composables hold the previous entry’s data on screen while the new one loads, rather than blanking the view back to pending:
The held value is a placeholder: it is never written to the cache, and the first load of all still reports pending. Turn it off with keepPreviousData: false where the stale content would mislead rather than reassure. useFronticPage already defaults to false, because there a changed key is a different page rather than the same one reloading.

Seeding Data That Arrived With the Page

Frontic lets a page define the data it needs, so a category page’s block can carry its listing in the same payload as the page. initialData hands that payload to the composable instead of fetching it again:
The seed is written to the cache entry, so the query reports success and stays quiet while it is within staleTime. It survives SSR: the entry is serialised into the payload, and the client hydrates it without re-fetching. Which raises the question of how old the payload is. The seed is treated as produced now unless you say otherwise, so a listing that travelled inside a cached page document is trusted as if it had just been fetched - stale prices and stock, marked fresh, for as long as staleTime lasts. Stamp it when the page document can outlive the request that renders it:
Prerendering, ISR and a CDN in front of the page all produce this: one document, many visitors, one timestamp that is not now for any of them.
Seed only data that is the response. A seeded query is indistinguishable from a fetched one - same status, same data - so nothing downstream can tell that the numbers came from somewhere else. To merely avoid a skeleton on data that might be wrong, leave keepPreviousData to do it.
Only the query the composable starts on is seeded. The payload answers one question - one set of filters, one sort, one page - so the first filter, sort or page the user picks fetches for real rather than being handed data that answers a different one.
The query it starts on is not always the plain one. initialFilters, initialSort and any state restored from the URL are applied before the seed is matched, so a filtered start is still a start. Seeding an unfiltered page payload there is accepted, marked fresh, and renders the wrong products - with nothing to distinguish it from a real response.Return undefined whenever the payload does not answer the question the search actually starts with:
In development the composable warns when it starts with filters, a sort, a search term or a page applied and initialData is set.
useFronticSearch takes the same single response in infinite mode and wraps it into the accumulated page set itself:

Manual Cache Control

All composables return two methods for cache control: