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
| Field | Type | Description |
|---|---|---|
info | Ref<SystemInfo> | Full system info (header / menus / ads / upload endpoints / analytics / …) |
channelCode | Ref<string> | Channel code cookie ref (persisted 30 days) |
shareCode | Ref<string> | Share code cookie ref (persisted 30 days) |
Getters (computed)
| Field | Type | Description |
|---|---|---|
isLoaded | Ref<boolean> | Whether system info has been loaded from the server (false = info is default fallback) |
menus | Ref<MenuItem[]> | Navigation menus (derived from info.menus, empty array fallback) |
hotKeywords | Ref<HotKeyword[]> | Hot keyword recommendations (derived from info.keywords) |
ads | Ref<AdSlots | null> | Ad slot configuration (derived from info.ads) |
imageDomain | Ref<string> | Image CDN domain prefix (derived from info.image_domain) |
Actions
| Method | Signature | Description |
|---|---|---|
setInfo(data) | (data: SystemInfo) => void | Write full system info and set _loaded=true (called by system.server on app init) |
setChannelCode(code) | (string) => void | Write channel code cookie (does NOT overwrite if already set, protects first-touch attribution) |
setShareCode(code) | (string) => void | Write share code cookie (same, no overwrite) |
getChannelCode() | () => string | Read channel code (for login API params, '' when unset) |
getShareCode() | () => string | Read share code (for login API params) |
initCode() | () => void | Read channelCode / shareCode from current route query → cookie (only when unset) |
reset() | () => void | Reset 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.tsearly 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=111from 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.
Related
userStore— user identity + token + device IDuseSeo— SEO meta injector (does not implicitly fall back tosystemStore.info.header— all SEO fields are explicit; header fields remain for business use, decoupled from SEO)- systemInfo ModuleOptions — fetch toggle + custom endpoint
Caveats
infostarts as a populated fallback, not empty — before the server response arrives it is the fully-shaped default object fromcreateDefaultSystemInfo(). Use theisLoadedgetter to tell whether real data arrived — don't testinfofor emptiness (it never is), and don't probe individual fields.- Channel / invite codes lock in on first write and are not overwritten —
setChannelCode/setShareCodedeliberately 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 — callingsetagain 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 frominfo.header, the page must read them and pass them touseSeo()itself (data flow: backend → store → page → useSeo). The framework never bypassesuseSeo()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.