Skip to main content
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.
nuxt.config.ts
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:
nuxt.config.ts
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.
fetchApiSecret
string
API secret for protected environments. Server-only - never exposed to the browser.

Routing

redirectOn301
boolean
default:"true"
Automatically redirect on 301 responses in useFronticPage.
throwOn404
boolean
default:"true"
Throw a 404 error when page is not found in useFronticPage.

Context

disableContext
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.
contextDomain
string
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
string
default:""
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.
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

name
keyof Blocks
required
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.
options
object
Configuration options.

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
() => Promise<void>
Refresh data using cache if still valid.
refetch
() => Promise<void>
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

name
keyof Listings
required
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.
options
object
Configuration options.

Returns

listing
Ref<Responses[T] | undefined>
The listing data with items and metadata.
status
Ref<'pending' | 'error' | 'success'>
Current query status.
refresh
() => Promise<void>
Refresh using cache if valid.
refetch
() => Promise<void>
Force fresh fetch.

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

name
keyof Listings
required
The name of the listing to fetch.
params
ListingParameters[T] | Ref
required
Parameters to pass to the listing endpoint.
options
object
Configuration options.

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.
searchTerm
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.
refresh
() => 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.
refetch
() => Promise<void>
Force a fresh fetch, completely bypassing the cache. Use this when you know data has changed and need guaranteed fresh results.
reset
() => 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
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()

resetSorting
() => Promise<void>
Reset to the backend’s default sort order. Equivalent to calling sortResult() without arguments.
await resetSorting()
Pagination Actions
loadNext
() => Promise<void>
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>
<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>
/**
 * 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?
  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.
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

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

Returns

page
Ref<Page | undefined>
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.
alternates
ComputedRef<AlternateRoute[] | undefined>
Alternate language URLs for SEO hreflang tags.
status
Ref<'pending' | 'error' | 'success'>
Current query status.
refresh
() => Promise<void>
Refresh using cache if valid.
refetch
() => Promise<void>
Force fresh fetch.

Usage

pages/[...slug].vue
<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

options
object
Configuration options.

Returns

contexts
Readonly<Ref<ContextOption[]>>
Available context options with regions and locales.
current
Readonly<Ref<Context | null>>
Current active context.
token
Readonly<Ref<string | null>>
The current context token.
update
(context: { region: string; locale: string }) => Promise<void>
Switch to a different region/locale combination.
isLoading
Readonly<Ref<boolean>>
Loading state during context operations.
refresh
() => Promise<void>
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

options
object
Configuration options for client behavior.

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.
1

Browser Request

Client sends request to your server at /api/stack
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.

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

Configure Cache Duration

const { block } = useFronticBlock('ProductFull', id, {
  staleTime: 1000 * 60 * 10, // 10 minutes
})

Manual Cache Control

All composables return two methods for cache control:
MethodDescription
refresh()Refresh data, using cache if still valid within staleTime
refetch()Force a fresh fetch, completely bypassing the cache