Quasar Boot Files: Centralized Initialization Across SPA, SSR, PWA, and Capacitor
Quasar boot files centralize plugin registration, API client setup, and global state initialization across SPA, SSR, PWA, and Capacitor targets—while guarding against SSR hydration errors and enabling per-target tree shaking.
18 Jun 2026, 18:37 UTC

The Problem: Scattered Initialization Logic
When a Quasar project targets multiple platforms—SPA, SSR, PWA, and Capacitor—initialization code tends to sprawl. API clients need auth tokens before the first request. Vue plugins must register before components mount. Analytics, feature flags, and global state stores all need a place to boot. Without a dedicated layer, developers shove this logic into main.js, component created() hooks, or scattered composables, leading to duplication, SSR crashes from window access, and hydration mismatches.
Quasar's boot files solve this by providing a single, ordered initialization layer that runs once before the Vue application is created, with full access to the Quasar context and platform-aware guards.
How Boot Files Work
A boot file lives under src/boot/ and exports an install function. Quasar invokes it with an object containing app (the Vue application instance), router, store (Pinia or Vuex), ssrContext, and platform helpers. The function can be synchronous or return a promise for async work.
// src/boot/api-client.js
export default async ({ app, store, ssrContext }) => {
const { $api } = await import('src/services/api')
// Register globally so components can use this.$api
app.config.globalProperties.$api = $api
// Provide for Composition API
app.provide('api', $api)
// Pinia store access
const auth = store('auth')
if (auth.token) {
$api.defaults.headers.common.Authorization = `Bearer ${auth.token}`
}
}
Register the boot file in quasar.conf.js under the boot array. Order matters—dependencies must precede dependents.
// quasar.conf.js
boot: [
'i18n', // runs first
'axios', // sets up $axios
'api-client', // uses $axios, needs auth store
'analytics' // uses router, runs last
]
SSR-Safe Guards and Platform Detection
Code inside a boot file executes on the server during SSR builds. Direct access to window, localStorage, or document throws errors and breaks hydration. Quasar provides Platform helpers and the ssrContext object to guard browser-only code.
// src/boot/analytics.js
import { Platform } from 'quasar'
export default ({ router, ssrContext }) => {
// Only initialize on client
if (Platform.is.client) {
import('src/services/analytics').then(({ initAnalytics }) => {
initAnalytics()
router.afterEach((to) => {
window.gtag('event', 'page_view', { page_path: to.fullPath })
})
})
}
// Server-side: optionally attach metadata to ssrContext for rendering
if (ssrContext) {
ssrContext.analyticsId = process.env.GA_ID
}
}
The Platform.is.client check (and its counterparts .is.server, .is.capacitor, .is.pwa) is evaluated at runtime, so the same boot file behaves correctly across all targets without code duplication.
Async Initialization and Per-Target Tree Shaking
Boot files can be asynchronous. This lets you await an auth token refresh, fetch feature flags, or warm a cache before the first paint. The trade-off: the Vue app won't mount until every async boot promise resolves. Heavy or network-dependent work here increases Time to First Paint (TTFP).
Quasar's build system tree-shakes boot files per target. In quasar.conf.js, you can declare platform-specific boots:
// quasar.conf.js
boot: [
'i18n',
'axios',
// Only included for Capacitor builds
...(ctx.mode.capacitor ? ['capacitor-plugins'] : []),
// Only for SSR
...(ctx.mode.ssr ? ['ssr-meta'] : []),
'api-client',
'analytics'
]
This keeps SPA and PWA bundles lean while Capacitor and SSR get their platform-specific initialization.
Worked Example: Auth-Aware API Client with SSR Guard
Create src/boot/api-client.js:
import { Platform } from 'quasar'
import { useAuthStore } from 'stores/auth'
import api from 'src/services/api'
export default async ({ app, store, ssrContext }) => {
const auth = useAuthStore(store)
// Attach token if present (works on client and server)
if (auth.token) {
api.defaults.headers.common.Authorization = `Bearer ${auth.token}`
}
// Client-only: refresh token on 401, persist to localStorage
if (Platform.is.client) {
api.interceptors.response.use(
(response) => response,
async (error) => {
if (error.response?.status === 401 && !error.config._retry) {
error.config._retry = true
try {
const { data } = await api.post('/auth/refresh')
auth.setToken(data.token)
api.defaults.headers.common.Authorization = `Bearer ${data.token}`
return api(error.config)
} catch {
auth.logout()
window.location.href = '/login'
}
}
return Promise.reject(error)
}
)
}
// Provide for Composition API
app.provide('api', api)
app.config.globalProperties.$api = api
}
Register it in quasar.conf.js after the Pinia boot (if using the official boot/pinia.js). Run quasar dev for SPA and quasar dev -m ssr for SSR. Open the browser console and the terminal running the SSR dev server. You should see the boot execute once per navigation on SSR (server log) and once on client hydration (browser log). Verify that window access inside the Platform.is.client block does not error during SSR.
Trade-offs and Limitations
- Execution order is implicit: Quasar runs boots in the array order. If
api-clientdepends onaxiosbut appears before it, you'll getundefinederrors. Document dependencies in comments or enforce order with a lint rule. - Async boots delay mounting: A slow token refresh or feature-flag fetch blocks the entire app. Move non-critical work to a background promise or a composable that loads lazily after mount.
- No hot-reload for boot files in SSR: Changes to boot files require a full restart of the SSR dev server. Plan accordingly during development.
Verify It Works
- Add a temporary
console.log('boot:api-client', { ssr: !!ssrContext })in your boot file. - Run
quasar dev(SPA) and confirm the log appears once in the browser console. - Run
quasar dev -m ssrand confirm the log appears in the terminal (server) and again in the browser (client hydration) withssr: truethenssr: false. - In a component, use
const api = inject('api')orthis.$apiand call an endpoint. Verify the Authorization header is sent. - Remove the temporary log.
If the boot runs once per target, guards prevent SSR errors, and the provided API client works in components without manual imports, the setup is correct.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.