systemStore

The framework's built-in system-config Pinia store. Composable name is useSystemStore (NOT useSystemInfoStore; source: src/runtime/store/system.ts).

Carries the full site config returned by the backend /system/info: SEO header, navigation menus, ad slots, hot keywords, upload endpoints, analytics, etc. All pages just call useSystemStore() to read — no manual fetch needed.

Basic usage

<script setup lang="ts">
const systemStore = useSystemStore()

// Read system info
const title = computed(() => systemStore.info.header?.site_title ?? 'Default Title')
const ogImage = computed(() => systemStore.info.header?.og_image)
</script>

<template>
  <h1>{{ title }}</h1>
  <nav v-if="systemStore.isLoaded">
    <NuxtLink v-for="m in systemStore.menus" :key="m.nav_id" :to="m.link || `/${m.code}`">
      {{ m.name }}
    </NuxtLink>
  </nav>
</template>

Auto initialization

The framework's built-in system.server.ts plugin auto-fetches /system/info during SSR and writes to this store — business code never calls fetch manually. On failure, system.client.ts re-fetches after client hydrate as a fallback.

For sites with no backend API, disable with systemInfo: false:

// nuxt.config.ts
swiftConfig: {
  systemInfo: false,                              // disable entirely
  // systemInfo: { ignorePaths: ['/preview'] },   // or path-level skip
}

There is no standalone path-exemption option for systemInfo; see framework option §systemInfo.

State

FieldTypeDescription
infoRef<SystemInfo>Full system info (header / menus / ads / upload endpoints / analytics / …)
channelCodeRef<string>Channel code cookie ref (persisted 30 days)
shareCodeRef<string>Share code cookie ref (persisted 30 days)

Getters (computed)

FieldTypeDescription
isLoadedRef<boolean>Whether system info has been loaded from the server (false = info is default fallback)
menusRef<MenuItem[]>Navigation menus (derived from info.menus, empty array fallback)
hotKeywordsRef<HotKeyword[]>Hot keyword recommendations (derived from info.keywords)
adsRef<AdSlots | null>Ad slot configuration (derived from info.ads)
imageDomainRef<string>Image CDN domain prefix (derived from info.image_domain)

Actions

MethodSignatureDescription
setInfo(data)(data: SystemInfo) => voidWrite full system info and set _loaded=true (called by system.server on app init)
setChannelCode(code)(string) => voidWrite channel code cookie (does NOT overwrite if already set, protects first-touch attribution)
setShareCode(code)(string) => voidWrite share code cookie (same, no overwrite)
getChannelCode()() => stringRead channel code (for login API params, '' when unset)
getShareCode()() => stringRead share code (for login API params)
initCode()() => voidRead channelCode / shareCode from current route query → cookie (only when unset)
reset()() => voidReset to default fallback values (testing only)

SystemInfo main type

interface SystemInfo {
  header?: SystemHeader      // SEO meta (site_title / keywords / description / og_image)
  site_url?: string          // Official primary domain (business use only — e.g. "Latest Address" jump link; unrelated to useSeo origin resolution, see framework#87)
  img_key?: string           // image decryption key (for .bnc files)
  websocket_key?: string
  image_domain?: string      // image CDN domain prefix
  upload_image_url?: string  // image upload endpoint
  upload_file_url?: string   // file upload endpoint
  menus?: MenuItem[]
  ads?: AdSlots
  keywords?: HotKeyword[]    // hot keyword recommendations
  domain_statistic?: DomainStatistic[]
  global_statistic?: string  // global analytics script snippet
  service_link?: string      // customer service link
  group_link?: string        // official community link
  permanent_url?: string     // permanent domain landing page
  tracker?: { appId?: string; channel?: string; encryptedConfig?: string; endpoint?: string; siteKey?: string }  // tracking channel config: appId+encryptedConfig = group WebSDK channel, endpoint(+siteKey) = self-hosted channel (#228); both absent = silently disabled
  // ...more fields (service_email / place_ad / contact_us / center_customer_* etc.)
  [key: string]: unknown     // backend may extend with arbitrary fields
}

interface SystemHeader {
  site_title?: string
  keywords?: string          // SEO keywords (comma-separated)
  description?: string       // SEO description
  og_image?: string          // OG / Twitter share image
  /** Legacy backend field, long-term compat fallback for og_image (backend protocol compat, not a deprecated framework API; always use og_image in new code) */
  img?: string
}

Full field list: src/runtime/store/system.ts top-of-file interface definitions.

Channel / share code semantics (important)

setChannelCode / setShareCode's "no-overwrite-if-set" semantic targets multiple visits in the same session (protects first-touch attribution). For cross-user scenarios, userStore.logout() clears cookies to reset — do NOT change to "always overwrite", or you'll break first-touch protection.

initCode() security policy:

  • Planting happens client-side (fw#212): called by system.client.ts early during hydration; SSR never writes the channel cookies — an SSR write would be replayed wholesale as Set-Cookie by the ISR cache, bypassing the "no-overwrite-if-set" protection
  • Defensive narrowing: under ?switch=1 (guest middleware's "logged-in user switching account" bypass) URL-injected channel/share codes are REJECTED, blocking attacks like /login?switch=1&channelCode=111 from seeding channel codes to unbound users.

/system/info response convention

After decryption by the framework's frameworkInternalRequest, the backend returns the data field shaped like:

{
  "header": {
    "site_title": "Swift Web Demo",
    "keywords": "demo,keywords",
    "description": "SEO description",
    "og_image": "https://cdn.example.com/og-default.jpg"
  },
  "site_url": "https://example.com",
  "img_key": "aes_image_decrypt_key",
  "image_domain": "https://img.example.com",
  "menus": [{ "name": "Home", "nav_id": "1", "code": "home", "link": "", "extend": "" }],
  "upload_image_url": "https://upload.example.com/image",
  "ads": { /* ... */ }
}

SSR fetch carries no channel params

system.server.ts does not send channel_code / share_code when fetching /system/info (to prevent the first visitor's params being baked into ISR cache and broadcast). Backend returns the "default channel" version; if your app needs channel-specific systemInfo, trigger a second fetch after channel cookie is written by auth.client.ts on hydrate (not built-in — implement on demand when real business needs it).

See Rendering Quick Guide §Channel differentiation.

  • userStore — user identity + token + device ID
  • useSeo — SEO meta injector (does not implicitly fall back to systemStore.info.header — all SEO fields are explicit; header fields remain for business use, decoupled from SEO)
  • systemInfo ModuleOptions — fetch toggle + custom endpoint

Caveats

  • info starts as a populated fallback, not empty — before the server response arrives it is the fully-shaped default object from createDefaultSystemInfo(). Use the isLoaded getter to tell whether real data arrived — don't test info for emptiness (it never is), and don't probe individual fields.
  • Channel / invite codes lock in on first write and are not overwritten — setChannelCode / setShareCode deliberately skip when a value already exists, so attribution keeps the original source; initCode() reads them from the current route query and persists them in a cookie for 30 days. Changing an existing value requires clearing the cookie first — calling set again does nothing.
  • SEO does not implicitly fall back to this store — useSeo() takes title / description only from what the page passes in. To use SEO-related fields from info.header, the page must read them and pass them to useSeo() itself (data flow: backend → store → page → useSeo). The framework never bypasses useSeo() to touch the head.
  • On ISR pages this is shared site config, not user state — site configuration is identical for every visitor, so caching it is safe. But channel / invite codes vary per visitor — never render them into an ISR page's HTML, or the first visitor's channel code gets baked in for everyone.