This module is currently in
open beta . We’d love to hear your feedback! If you have questions or suggestions, please
get in touch with us .
The @frontic/nuxt module provides seamless integration between your Nuxt application and Frontstack backend. It includes composables for data fetching, automatic proxy configuration, and locale management.
Installation
npx nuxi@latest module add @frontic/nuxt
This installs the module and adds it to your nuxt.config.ts automatically.
export default defineNuxtConfig ({
modules: [ '@frontic/nuxt' ] ,
})
The module works out of the box with sensible defaults: CORS proxy enabled, all composables registered, and TypeScript paths configured.
Configuration
All options are optional. Customize behavior as needed:
export default defineNuxtConfig ({
modules: [ '@frontic/nuxt' ] ,
frontic: {
// All options below are optional
contextDomain: 'www.your-shop.com' ,
proxy: '/api/custom-path' ,
redirectOn301: false ,
throwOn404: false ,
// ... other options
} ,
})
Options
API
proxy
boolean | string
default: "true"
Enable the built-in CORS proxy at /api/stack, or set a custom path. Enable proxy at /api/stack.
Disable proxy (use for external proxy setups).
Custom proxy path (e.g., /api/custom-frontic).
API secret for protected environments. Server-only - never exposed to the browser. frontic : {
fetchApiSecret : process . env . FRONTSTACK_FETCH_SECRET ,
}
The secret is injected into requests via the server proxy, keeping it secure.
Routing
Automatically redirect on 301 responses in useFronticPage.
Throw a 404 error when page is not found in useFronticPage.
Context
Disable automatic context fetching and cookie management in useFronticContext.
The context endpoint will not be called automatically on mount
No context cookie will be set automatically
You must manually call refresh() to fetch contexts
You are responsible for storing and managing the context token
Use this when you want full control over context management, or when integrating with an existing locale/region system.
contextCookieName
string
default: "fs-context"
Cookie name for storing the context token.
Cookie max age in seconds. Default is 1 year.
Override domain for API requests. Used by useFronticClient methods for context resolution.
Frontic UI
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 .
Prefix for auto-imported Frontic UI components from your component directory.
componentDir
string
default: "@/components/ui"
Directory path for Frontic UI components to auto-import.
Composables
composables
boolean | FronticComposable[]
default: "true"
Control which composables are auto-imported during the Nuxt build. Disable all composable imports.
Import only specific composables. Valid values:
'block' → useFronticBlock
'listing' → useFronticListing
'search' → useFronticSearch
'page' → useFronticPage
'context' → useFronticContext
'client' → useFronticClient
When a composable is disabled , it will not be auto-imported during the Nuxt build. This means you won’t get type errors or warnings - the composable simply 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 - so you get instant UI updates with stale-while-revalidate, shared requests across components, and seamless server-to-client data transfer without any extra configuration.
useFronticBlock
Fetch a single block (product, category, brand) by key with automatic caching and SSR support. See Caching for details.
Parameters
The name of the block to fetch. Provides full autocomplete for your generated block types.
key
string | Ref<string>
required
The key identifier for the block. Can be reactive for dynamic fetching.
Configuration options. Context token for locale/region selection.
Context domain to override the global setting for this request.
Request URL for analytics/tracking purposes.
Cache duration in milliseconds. Default: 5 minutes.
Returns
block
Ref<Responses[T] | undefined>
The block data, fully typed based on the block name.
status
Ref<'pending' | 'error' | 'success'>
Current query status for loading states.
Refresh data using cache if still valid.
Force a fresh fetch, bypassing cache entirely.
Usage
< script setup lang = "ts" >
const route = useRoute ()
const { block : product , status } = useFronticBlock ( 'ProductFull' , route . params . id )
</ script >
< template >
< div v - if = "status === 'pending'" > Loading ...</ div >
< div v - else - if = "product" >
< h1 >{{ product . name }} </ h1 >
< p >{{ product . price . formatted }} </ p >
</ div >
</ template >
// With options
const { block } = useFronticBlock ( 'ProductCard' , productId , {
contextKey: contextToken ,
staleTime: 1000 * 60 * 10 , // 10 minutes
})
// Reactive key - automatically refetches when key changes
const productKey = ref ( 'product-123' )
const { block } = useFronticBlock ( 'ProductCard' , productKey )
import type { Ref , MaybeRef } from 'vue'
import type { Blocks , Responses } from '@frontic/stack/generated-types'
function useFronticBlock < T extends keyof Blocks >(
name : T ,
key : MaybeRef < string >,
options ?: {
contextKey ?: MaybeRef < string | undefined >
contextDomain ?: MaybeRef < string | undefined >
requestUrl ?: MaybeRef < string | undefined >
staleTime ?: number
}
) : {
block : Ref < Responses [ T ] | undefined >
status : Ref < 'pending' | 'error' | 'success' >
refresh : () => Promise < void >
refetch : () => Promise < void >
}
useFronticListing
Fetch a listing by name and parameters with automatic caching and SSR support. Use this when you need to display items with simple interactive filtering or sorting. See Caching for details.
Building a page with interactive search, filters, sorting, or pagination? Use
useFronticSearch instead.
Parameters
The name of the listing to fetch.
params
ListingParameters[T] | Ref
required
Parameters to pass to the listing endpoint. Type-safe based on listing name.
Configuration options. Context token for locale/region selection.
Context domain to override the global setting for this request.
Request URL for analytics/tracking purposes.
Cache duration in milliseconds.
Returns
listing
Ref<Responses[T] | undefined>
The listing data with items and metadata.
status
Ref<'pending' | 'error' | 'success'>
Current query status.
Refresh using cache if valid.
Usage
< script setup lang = "ts" >
const { listing , status } = useFronticListing ( 'CategoryProducts' , {
key: 'shoes' ,
})
</ script >
< template >
< div v - if = "status === 'pending'" > Loading ...</ div >
< div v - else >
< div v - for = "item in listing?.items" : key = "item.key" > {{ item . name }} </ div >
</ div >
</ template >
// Reactive params - refetches when category changes
const categoryKey = ref ( 'shoes' )
const { listing } = useFronticListing (
'CategoryProducts' ,
computed (() => ({ key: categoryKey . value }))
)
import type { Ref , MaybeRef } from 'vue'
import type { Listings , ListingParameters , Responses } from '@frontic/stack/generated-types'
function useFronticListing < T extends keyof Listings >(
name : T ,
params : MaybeRef < ListingParameters [ T ]>,
options ?: {
contextKey ?: MaybeRef < string | undefined >
contextDomain ?: MaybeRef < string | undefined >
requestUrl ?: MaybeRef < string | undefined >
staleTime ?: number
}
) : {
listing : Ref < Responses [ T ] | undefined >
status : Ref < 'pending' | 'error' | 'success' >
refresh : () => Promise < void >
refetch : () => Promise < void >
}
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
The name of the listing to fetch.
params
ListingParameters[T] | Ref
required
Parameters to pass to the listing endpoint.
Configuration options. When true, the searchTerm ref is ignored and no search parameter is sent to the API. Use this when your listing doesn’t support text search.
Minimum number of characters required before a search request is triggered. Prevents API calls for very short, likely meaningless queries.
Debounce delay in milliseconds for search input. Controls how long to wait after the user stops typing before triggering a search request. Increase for slower networks or to reduce API calls.
When true, filter methods (addFilter, removeFilter, etc.) are disabled and state.available.filter remains empty. Use this when your UI doesn’t need filtering controls.
A list of filter field names that should use OR logic, allowing users to select multiple values (e.g., “red OR blue”). Fields not listed use AND logic by default. orFilter : [ 'properties.color' , 'properties.size' ]
A list of filter field keys to control which filters appear in state.available.filter. Combined with mode to include or exclude specific filters. filter : {
select : [ 'properties.color' , 'properties.size' , 'properties.brand' ]
}
filter.mode
'include' | 'exclude'
default: "include"
Controls how select is applied. With 'include', only the specified fields are shown. With 'exclude', all filters except the specified fields are shown.
Human-readable labels for filter fields. These appear in state.available.filter for building filter UI headings. filter : {
label : {
'properties.color' : 'Color' ,
'properties.size' : 'Size' ,
'properties.brand' : 'Brand'
}
}
Define the display order for filters. Keys listed here appear first in state.available.filter in the specified order. Any filters not in this array appear after in their original order. filter : {
sort : [ 'properties.color' , 'properties.size' , 'properties.brand' ]
}
When true, sorting methods (sortResult, resetSorting) are disabled and state.available.sorting remains empty. Use this when your UI doesn’t need sorting controls.
A list of sort options to control which sorts appear in state.available.sorting. Use the format 'field:order' (e.g., 'name:asc'). Use 'default' for the backend’s default sorting. sorting : {
select : [ 'default' , 'name:asc' , 'name:desc' , 'price.amount:asc' ]
}
sorting.mode
'include' | 'exclude'
default: "include"
Controls how select is applied. With 'include', only the specified sort options are shown. With 'exclude', all sorts except the specified options are shown.
Human-readable labels for sort options. These appear in state.available.sorting for building sort dropdowns. Use 'default' key for the backend’s default sorting. sorting : {
label : {
'default' : 'Relevance' ,
'name:asc' : 'Name A-Z' ,
'name:desc' : 'Name Z-A' ,
'price.amount:asc' : 'Price: Low to High' ,
'price.amount:desc' : 'Price: High to Low'
}
}
Define the display order for sort options. Keys listed here appear first in state.available.sorting in the specified order. Any sorts not in this array appear after in their original order. sorting : {
sort : [ 'default' , 'price.amount:asc' , 'price.amount:desc' , 'name:asc' ]
}
Context token for locale/region.
Context domain to override the global setting for this request.
Request URL for analytics/tracking purposes.
Cache duration in milliseconds.
Custom cache key for state sharing across components.
Returns
State & Cache Control
data
Ref<Responses[T] | undefined>
The full listing response from the API, containing items, pagination metadata, and filter facets. // Access pagination info
const hasMore = data . value ?. page ?. next !== undefined
// Access filter facets
data . value ?. filter
result
Ref<unknown[] | undefined>
The items array from the listing response. Use this to render your product grid or list. // Iterate over items
result . value ?. forEach ( item => console . log ( item . name ))
status
Ref<'pending' | 'error' | 'success'>
Current query status. Use this to show loading spinners or error states in your UI. < div v - if = "status === 'pending'" > Loading ...</ div >
< div v - else - if = "status === 'error'" > Something went wrong </ div >
< div v - else > {{ result ?. length }} products found </ div >
state
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. Show SearchState Properties
Currently active query parameters. filter
Partial<Record<keyof TFilters, unknown>>
Active filter values by field. Keys are typed based on your listing’s filter schema -get full autocomplete. // Keys are typed - autocomplete available!
state . value . active . filter [ 'properties.color' ] // ✅ autocomplete
state . value . active . filter [ 'invalid.field' ] // ❌ type error
sorting
{ field: keyof TSorts; order: 'asc' | 'desc' } | undefined
Currently applied sort (typed based on listing schema), or undefined when using default sorting.
Current search term (empty string when not searching).
Current page number (1-indexed).
Counts for active state. Number of filter fields with active selections. Use this to show a badge on your “Filters” button. < button > Filters < span v - if = "state.active.count.filter" > ({{ state . active . count . filter }}) </ span > </ button >
Number of active sorts (0 or 1).
Available options for UI controls. Pre-processed filter options ready for rendering. Each filter’s key is typed based on your listing schema. interface UiFilter < TFilters > {
key : keyof TFilters // Typed! e.g., 'properties.color'
label : string // e.g., 'Color' (from your config)
options : Array <{
option : string // Display name, e.g., 'Red'
value : string // API value, e.g., 'red'
count : number // Number of matching items
selected : boolean
disabled : boolean
}>
}
Pre-processed sort options ready for rendering dropdowns. Keys are typed based on your listing schema. interface UiSort < TSorts > {
key : keyof TSorts | 'default' // Typed!
label : string // e.g., 'Name A-Z' (from your config)
value : keyof TSorts | 'default'
}
Whether a next page is available. Use for pagination controls.
Whether a previous page is available. Use for pagination controls.
Counts for available options. Number of filter fields available.
Number of sort options available.
Total number of items matching the current query. < p >{{ state . available . count . result }} products found </ p >
Total number of pages. Use this to display “Page X of Y”. < p > Page {{ state . active . page }} of {{ state . available . count . page }} </ p >
Two-way bindable search term. Connect this directly to your search input - typing automatically triggers debounced API requests when the term exceeds searchTermThreshold.
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.
Force a fresh fetch, completely bypassing the cache. Use this when you know data has changed and need guaranteed fresh results.
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
addFilter
(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. // User clicks "Red" checkbox
await addFilter ( 'properties.color' , 'red' )
// User also selects "Blue" - both are now active
await addFilter ( 'properties.color' , 'blue' )
removeFilter
(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. // User unchecks "Red"
await removeFilter ( 'properties.color' , 'red' )
// User clicks "Clear all colors"
await removeFilter ( 'properties.color' )
filterResult
(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. // Single-select dropdown changed to "Large"
await filterResult ( 'properties.size' , [ 'large' ])
// Set multiple values at once
await filterResult ( 'properties.color' , [ 'red' , 'blue' , 'green' ])
resetFilter
(field?) => Promise<void>
Clear filters for a specific field, or all filters if no field is provided. Use this for “Reset” buttons. // Clear just color filters
await resetFilter ( 'properties.color' )
// Clear ALL filters
await resetFilter ()
Sort Actions
sortResult
(sortBy?: string) => Promise<void>
Apply a sort order using the 'field:order' format. Call without arguments to reset to default sorting. // Sort by name ascending
await sortResult ( 'name:asc' )
// Sort by price descending
await sortResult ( 'price.amount:desc' )
// Reset to default (backend's default order)
await sortResult ()
Reset to the backend’s default sort order. Equivalent to calling sortResult() without arguments.
Pagination Actions
Load the next page and append items to existing results. Use this for infinite scroll or “Load More” buttons. < button v - if = "data?.page?.next" @ click = "loadNext" >
Load More Products
</ button >
paginate
(direction: 'next' | 'prev') => Promise<boolean>
Navigate to the next or previous page, replacing the current results. Returns true if navigation was successful, false if there’s no page in that direction.
Search Actions
Clear the search term and refresh results. Use this for a “clear search” button. < button v - if = "searchTerm" @ click = "resetSearch" > ✕ </ button >
Usage
< script setup lang = "ts" >
const { data , result , state , searchTerm , addFilter , removeFilter , sortResult , loadNext , reset } = useFronticSearch (
'CategoryProducts' ,
{ key: 'shoes' },
{
orFilter: [ 'properties.color' , 'properties.size' ],
filter: {
select: [ 'properties.color' , 'properties.size' , 'properties.brand' ],
label: { 'properties.color' : 'Color' , 'properties.size' : 'Size' },
sort: [ 'properties.color' , 'properties.size' ], // Display order for filters
},
sorting: {
label: {
default: 'Relevance' ,
'name:asc' : 'Name A-Z' ,
'price.amount:asc' : 'Price: Low to High' ,
},
sort: [ 'default' , 'price.amount:asc' , 'name:asc' ], // Display order for sorts
},
}
)
</ script >
< template >
< input v - model = "searchTerm" placeholder = "Search..." />
< button v - if = "state.active.count.filter > 0" @ click = "reset" > Clear All </ button >
< div v - for = "item in result" : key = "item.key" > {{ item . name }} </ div >
< button v - if = "data?.page?.next" @ click = "loadNext" > Load More </ button >
</ template >
Full Example with Filters
< script setup lang = "ts" >
const { data , result , state , searchTerm , addFilter , removeFilter , sortResult , loadNext } = useFronticSearch (
'CategoryProducts' ,
{ key: 'shoes' },
{
orFilter: [ 'properties.color' , 'properties.size' ],
filter: {
select: [ 'properties.color' , 'properties.size' , 'properties.brand' ],
label: {
'properties.color' : 'Color' ,
'properties.size' : 'Size' ,
'properties.brand' : 'Brand' ,
},
},
sorting: {
label: {
default: 'Relevance' ,
'name:asc' : 'Name A-Z' ,
'name:desc' : 'Name Z-A' ,
'price.amount:asc' : 'Price: Low to High' ,
'price.amount:desc' : 'Price: High to Low' ,
},
},
}
)
function toggleFilter ( field : string , value : string , isSelected : boolean ) {
if ( isSelected ) {
removeFilter ( field , value )
} else {
addFilter ( field , value )
}
}
</ script >
< template >
< div class = "search-page" >
<!-- Search Input -->
< input v - model = "searchTerm" type = "search" placeholder = "Search products..." />
<!-- Sort Dropdown -->
< select @ change = "sortResult(($event.target as HTMLSelectElement).value)" >
< option v - for = "sort in state.available.sorting" : key = "sort.key" : value = "sort.key" : selected = "state.active.sorting?.field === sort.key.split(':')[0]" >
{{ sort . label }}
</ option >
</ select >
<!-- Filter Sidebar -->
< aside >
< div v - for = "filter in state.available.filter" : key = "filter.key" >
< h4 >{{ filter . label }} </ h4 >
< label v - for = "option in filter.options" : key = "option.value" >
< input type = "checkbox" : checked = "option.selected" @ change = "toggleFilter(filter.key, option.value, option.selected)" />
{{ option . label }} ({{ option . count }})
</ label >
</ div >
</ aside >
<!-- Results -->
< main >
< p >{{ state . available . count . result }} products found </ p >
< div class = "product-grid" >
< article v - for = "item in result" : key = "item.key" >
< h3 >{{ item . name }} </ h3 >
< p >{{ item . price . formatted }} </ p >
</ article >
</ div >
< button v - if = "data?.page?.next" @ click = "loadNext" > Load More </ button >
</ main >
</ div >
</ template >
Shared State with Wrapper Composable
composables/useMySearch.ts
pages/index.vue
components/Filters.vue
/**
* Wrapper composable for product search.
*
* By using a fixed cacheKey, all components calling this composable
* share the same search state automatically via Nuxt's useState.
*/
export function useMySearch () {
return useFronticSearch (
'CategoryProducts' ,
{ key: 'shoes' },
{
// This cacheKey enables state sharing across components!
cacheKey: 'product-search' ,
// Search configuration
orFilter: [ 'properties.color' , 'properties.size' ],
filter: {
select: [ 'properties.color' , 'properties.size' , 'properties.brand' ],
label: {
'properties.color' : 'Color' ,
'properties.size' : 'Size' ,
'properties.brand' : 'Brand' ,
},
},
sorting: {
label: {
default: 'Relevance' ,
'name:asc' : 'Name A-Z' ,
'price.amount:asc' : 'Price: Low to High' ,
},
},
}
)
}
Why use a wrapper composable?
Centralized configuration - All your filter options, sort labels, and search settings live in one file. Need to add a new filter? Update it once.
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.
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.
import type { Ref , MaybeRef } from 'vue'
import type { Listings , ListingParameters , Responses } from '@frontic/stack/generated-types'
// Typed filter for UI rendering
interface UiFilter < TFilters = Record < string , unknown >> {
key : keyof TFilters & string
label : string
options : FilterOption []
}
// Typed sort for UI rendering
interface UiSort < TSorts = Record < string , unknown >> {
key : ( keyof TSorts & string ) | 'default'
label : string
value : ( keyof TSorts & string ) | 'default'
}
// Fully typed search state
interface SearchState < TFilters = Record < string , unknown >, TSorts = Record < string , unknown >> {
active : {
filter : Partial < Record < keyof TFilters & string , unknown >>
sorting : { field : keyof TSorts & string ; order : 'asc' | 'desc' } | undefined
search : string
page : number
count : {
filter : number
sorting : number
}
}
available : {
filter : UiFilter < TFilters >[]
sorting : UiSort < TSorts >[]
nextPage : boolean
prevPage : boolean
count : {
filter : number
sorting : number
result : number
page : number
}
}
}
function useFronticSearch < TSearch extends keyof Listings , TFilters extends Record < string , unknown >, TSorts extends Record < string , unknown >>(
name : TSearch ,
params : MaybeRef < ListingParameters [ TSearch ]>,
options ?: {
contextKey ?: MaybeRef < string | undefined >
contextDomain ?: MaybeRef < string | undefined >
requestUrl ?: MaybeRef < string | undefined >
staleTime ?: number
cacheKey ?: string
searchTermThreshold ?: number
searchDebounce ?: number
orFilter ?: Array < keyof TFilters & string >
disableSearch ?: boolean
disableFilter ?: boolean
disableSorting ?: boolean
filter ?: {
select ?: Array < keyof TFilters & string >
mode ?: 'include' | 'exclude'
label ?: Partial < Record < keyof TFilters & string , string >>
sort ?: Array < keyof TFilters & string > // Display order for filters
}
sorting ?: {
select ?: Array < keyof TSorts & string >
mode ?: 'include' | 'exclude'
label ?: Record < string , string >
sort ?: Array < keyof TSorts & string > // Display order for sorts
}
}
) : {
data : Ref < Responses [ TSearch ] | undefined >
result : Ref < unknown [] | undefined >
status : Ref < 'pending' | 'error' | 'success' >
state : Ref < SearchState < TFilters , TSorts >> // Fully typed state
searchTerm : Ref < string >
loadNext : () => Promise < void >
refresh : () => Promise < void >
refetch : () => Promise < void >
paginate : ( direction : 'next' | 'prev' ) => Promise < boolean >
sortResult : ( sortBy ?: string ) => Promise < void >
resetSorting : () => Promise < void >
filterResult : < K extends keyof TFilters >( field : K , values : TFilters [ K ][]) => Promise < void >
addFilter : < K extends keyof TFilters >( field : K , value : TFilters [ K ]) => Promise < void >
removeFilter : < K extends keyof TFilters >( field : K , value ?: TFilters [ K ]) => Promise < void >
resetFilter : ( field ?: keyof TFilters & string ) => Promise < void >
resetSearch : () => Promise < void >
reset : () => Promise < void > // Reset all state (search, filters, sorting)
}
useFronticPage
Dynamic page routing with automatic slug detection, redirect handling, and 404 errors. Includes automatic caching and SSR support. See Caching for details.
Parameters
The page slug. If omitted, auto-detected from current URL.
Configuration options. Automatically redirect on 301 responses. Uses module config default.
Throw a 404 error when page is not found. Uses module config default.
Context token for locale/region selection.
Context domain to override the global setting for this request.
Request URL for analytics/tracking purposes.
Cache duration in milliseconds.
Returns
The full page response object.
data
ComputedRef<Page['data'] | undefined>
The page data payload for rendering.
type
ComputedRef<string | undefined>
The page type for conditional rendering ('ProductCategory', 'ProductDetail', etc.).
block
ComputedRef<string | undefined>
The block name to render for this page.
route
ComputedRef<PageRoute | undefined>
Route information including redirect and context data. Show PageRoute Properties
HTTP status code (200, 301, 404).
Redirect destination with target path.
Suggested context for locale switching.
alternates
ComputedRef<AlternateRoute[] | undefined>
Alternate language URLs for SEO hreflang tags.
status
Ref<'pending' | 'error' | 'success'>
Current query status.
Refresh using cache if valid.
Usage
< script setup lang = "ts" >
const { data , block , alternates , status } = useFronticPage ()
useHead ({
link:
alternates . value ?. map (( alt ) => ({
rel: 'alternate' ,
hreflang: alt . locale ,
href: alt . href ,
})) ?? [],
})
</ script >
< template >
< div v - if = "status === 'pending'" > Loading ...</ div >
< component v - else : is = "resolveComponent(block)" : data = "data" />
</ template >
// With explicit slug
const { page } = useFronticPage ( 'demo-shop.com/uk/products/shoes' )
// Disable redirect for manual handling
const { page , route } = useFronticPage ( undefined , { redirectOn301: false })
if ( route . value ?. redirect ) {
navigateTo ( route . value . redirect . path )
}
// Disable 404 throwing for custom error handling
const { page , route } = useFronticPage ( undefined , { throwOn404: false })
if ( route . value ?. code === 404 ) {
// Custom 404 handling
}
import type { Ref , ComputedRef , MaybeRef } from 'vue'
interface Page {
id ?: string
slug ?: string
title ?: string
meta ?: Record < string , unknown >
data ?: Record < string , unknown >
route ?: PageRoute
}
interface PageRoute {
code ?: number
redirect ?: { path : string }
alternates ?: AlternateRoute []
context ?: { suggested ?: string }
}
interface AlternateRoute {
locale ?: string
lang ?: string
href : string
}
function useFronticPage (
slug ?: MaybeRef < string >,
options ?: {
redirectOn301 ?: boolean
throwOn404 ?: boolean
contextKey ?: MaybeRef < string | undefined >
contextDomain ?: MaybeRef < string | undefined >
requestUrl ?: MaybeRef < string | undefined >
staleTime ?: number
}
) : {
page : Ref < Page | undefined >
data : ComputedRef < Page [ 'data' ] | undefined >
type : ComputedRef < string | undefined >
block : ComputedRef < string | undefined >
route : ComputedRef < PageRoute | undefined >
alternates : ComputedRef < AlternateRoute [] | undefined >
status : Ref < 'pending' | 'error' | 'success' >
refresh : () => Promise < void >
refetch : () => Promise < void >
}
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.
Parameters
Configuration options. Disable automatic context fetching and cookie management. When true, you must manually call refresh() to fetch contexts and manage the token yourself. Overrides the module-level
disableContext setting.
cookieName
string
default: "fs-context"
Cookie name for persisting the context token.
Cookie max age in seconds. Default: 1 year.
Returns
contexts
Readonly<Ref<ContextOption[]>>
Available context options with regions and locales. Show ContextOption Properties
Region code (e.g., 'uk', 'de').
Currency code (e.g., 'GBP', 'EUR').
locales
Array<{ key: string; url: string }>
Available locales with their URLs.
current
Readonly<Ref<Context | null>>
Current active context. Current region (e.g., 'uk').
Current locale (e.g., 'en-gb').
Current scope (e.g., 'b2c').
Context identifier token.
token
Readonly<Ref<string | null>>
The current context token.
update
(context: { region: string; locale: string }) => Promise<void>
Switch to a different region/locale combination.
Loading state during context operations.
Manually refresh available contexts.
Usage
< script setup lang = "ts" >
const { contexts , current , update , isLoading } = useFronticContext ()
function getDefaultLocale ( region : string ) {
return contexts . value . find (( c ) => c . region === region )?. locales [ 0 ]?. key ?? 'en'
}
</ script >
< template >
< select
: value = "current?.region"
: disabled = "isLoading"
@ change = "
update( {
region : $event . target . value ,
locale : getDefaultLocale ( $event . target . value ),
})
"
>
< option v - for = "ctx in contexts" : key = "ctx.region" : value = "ctx.region" > {{ ctx . region . toUpperCase () }} ({{ ctx . currency }}) </ option >
</ select >
</ template >
import type { Ref , Readonly } from 'vue'
interface Context {
region : string
locale : string
scope : string
token : string
}
interface ContextOption {
region : string
currency : string
locales : Array <{ key : string ; url : string }>
}
function useFronticContext ( options ?: { cookieName ?: string ; cookieMaxAge ?: number ; disableContext ?: boolean }) : {
contexts : Readonly < Ref < ContextOption []>>
current : Readonly < Ref < Context | null >>
token : Readonly < Ref < string | null >>
update : ( context : { region : string ; locale : string }) => Promise < void >
isLoading : Readonly < Ref < boolean >>
refresh : () => Promise < void >
}
useFronticClient
Low-level client for direct API access. All other composables use this internally.
Parameters
Configuration options for client behavior. Override proxy behavior: true (force proxy), false (direct API), or custom path.
Override context domain: false (disable) or specific domain.
Override request URL tracking: false (disable) or specific URL.
API secret for server-side requests.
Returns
Returns a FrontstackClient instance with type-safe methods:
block
<T>(name, key, config?) => Promise<Responses[T]>
Fetch a block by name and key.
listing
<T>(name, params, config?) => Promise<Responses[T]>
Fetch a listing with parameters and query options.
page
(slug, config?) => Promise<Page>
Fetch a page by its slug.
context
(token, config?) => Promise<Context>
Get context by token.
contextList
(token?, config?) => Promise<[ContextOption[], string]>
Get available contexts.
contextUpdate
(context, token, config?) => Promise<Context>
Update context with new region/locale.
Usage
const client = useFronticClient ()
// Fetch a product block
const product = await client . block ( 'ProductFull' , 'product-123' )
// Fetch a listing with filters
const listing = await client . listing (
'CategoryProducts' ,
{ key: 'shoes' },
{
query: {
filter: [{ type: 'equals' , field: 'properties.color' , value: 'Red' }],
sort: { field: 'price.amount' , order: 'asc' },
limit: 20 ,
},
}
)
// Fetch page data
const page = await client . page ( 'demo-shop.com/uk/products' )
// Client with custom configuration
const directClient = useFronticClient ({ proxy: false })
const customProxyClient = useFronticClient ({ proxy: '/api/custom-frontic' })
import type { Blocks , Listings , ListingParameters , Responses } from '@frontic/stack/generated-types'
interface RequestOptions {
requestUrl ?: string
contextKey ?: string
contextDomain ?: string
proxyUrl ?: string
}
interface FrontstackClient {
block : < T extends keyof Blocks >( name : T , key : string , config ?: RequestOptions ) => Promise < Responses [ T ]>
listing : < T extends keyof Listings >(
name : T ,
parameters : ListingParameters [ T ],
config ?: {
query ?: {
filter ?: Filter []
sort ?: Sort | Sort []
search ?: string
limit ?: number
page ?: number
}
} & RequestOptions
) => Promise < Responses [ T ]>
page : ( slug : string , config ?: RequestOptions ) => Promise < Page >
context : ( token : string , config ?: RequestOptions ) => Promise < Context >
contextList : ( token ?: string , config ?: RequestOptions ) => Promise <[ ContextOption [], string ]>
contextUpdate : ( context : { region : string ; locale : string }, token : string , config ?: RequestOptions ) => Promise < Context >
}
function useFronticClient ( options ?: { proxy ?: boolean | string ; contextDomain ?: false | string ; requestUrl ?: false | string ; secret ?: string }) : FrontstackClient
Proxy
The module includes a built-in proxy to prevent CORS issues on client-side requests.
Browser Request
Client sends request to your server at /api/stack
Server Forwards
Your Nuxt server forwards the request to the Fetch API
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.
TypeScript
The module configures a path alias so you can import from your generated Frontstack client:
import type { ProductCard , ProductFull } from '@frontic/stack/generated-types'
import { createClient } from '@frontic/stack/generated-client'
This maps @frontic/stack/* to .frontstack/* in your project root, where the Frontstack CLI generates your typed client.
All composables are fully typed with generics that preserve the specific block/listing types through to the return values, providing full IDE autocomplete for response data.
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
const { block } = useFronticBlock ( 'ProductFull' , id , {
staleTime: 1000 * 60 * 10 , // 10 minutes
})
Manual Cache Control
All composables return two methods for cache control:
Method Description refresh()Refresh data, using cache if still valid within staleTime refetch()Force a fresh fetch, completely bypassing the cache