Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Vue 3 debugging reference for reactivity issues, computed errors, watcher bugs, async failures, and SSR hydration problems.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
reference/ssr-platform-specific-apis.md
1---2title: Guard Platform-Specific APIs in Universal SSR Code3impact: HIGH4impactDescription: Accessing browser-only APIs on server causes crashes; Node.js APIs fail in browser5type: gotcha6tags: [vue3, ssr, browser-api, nodejs, universal, isomorphic, server-side-rendering]7---89# Guard Platform-Specific APIs in Universal SSR Code1011**Impact: HIGH** - SSR applications run the same code on both server (Node.js) and client (browser). Browser APIs like `window`, `document`, and `localStorage` don't exist in Node.js and will throw `ReferenceError`. Similarly, Node.js APIs like `fs` and `process` aren't available in browsers.1213Universal/isomorphic code must guard platform-specific API access or use libraries that work on both platforms.1415## Task Checklist1617- [ ] Never access `window`, `document`, `navigator` in `setup()` or `created()`18- [ ] Move browser API access to `onMounted()` lifecycle hook19- [ ] Use `typeof window !== 'undefined'` guard when needed outside lifecycle20- [ ] Use cross-platform libraries for common functionality (fetch, storage)21- [ ] Use Nuxt's `process.client` / `process.server` guards in Nuxt projects2223## Common Browser APIs That Break SSR2425| API | Node.js Behavior |26|-----|-----------------|27| `window` | `ReferenceError: window is not defined` |28| `document` | `ReferenceError: document is not defined` |29| `localStorage` / `sessionStorage` | `ReferenceError` |30| `navigator` | `ReferenceError` |31| `location` | `ReferenceError` |32| `history` | `ReferenceError` |33| `alert` / `confirm` / `prompt` | `ReferenceError` |34| `requestAnimationFrame` | `ReferenceError` |35| `IntersectionObserver` | `ReferenceError` |36| `ResizeObserver` | `ReferenceError` |3738**Incorrect - Crashes on Server:**39```javascript40// WRONG: These run during setup/SSR - crashes in Node.js41const width = ref(window.innerWidth)42const theme = localStorage.getItem('theme')43const userAgent = navigator.userAgent44```4546```vue47<script setup>48import { ref } from 'vue'4950// WRONG: Runs on server, crashes51const scrollY = ref(window.scrollY)5253// WRONG: document doesn't exist on server54document.title = 'My Page'55</script>56```5758**Correct - Use onMounted:**59```vue60<script setup>61import { ref, onMounted, onUnmounted } from 'vue'6263// Safe defaults that work on server64const width = ref(0)65const theme = ref('light')66const scrollY = ref(0)6768onMounted(() => {69// Browser APIs only accessed after mount (client-only)70width.value = window.innerWidth71theme.value = localStorage.getItem('theme') || 'light'72scrollY.value = window.scrollY7374// Event listeners safe in mounted75window.addEventListener('resize', handleResize)76window.addEventListener('scroll', handleScroll)77})7879onUnmounted(() => {80window.removeEventListener('resize', handleResize)81window.removeEventListener('scroll', handleScroll)82})8384function handleResize() {85width.value = window.innerWidth86}8788function handleScroll() {89scrollY.value = window.scrollY90}91</script>92```9394**Correct - Guard with typeof:**95```javascript96// When you need to check outside lifecycle hooks97function getStoredValue(key, defaultValue) {98if (typeof window !== 'undefined' && window.localStorage) {99return localStorage.getItem(key) ?? defaultValue100}101return defaultValue102}103104// Composable with SSR awareness105export function useMediaQuery(query) {106const matches = ref(false)107108// Only run on client109if (typeof window !== 'undefined') {110const mediaQuery = window.matchMedia(query)111matches.value = mediaQuery.matches112113// Setup listener in lifecycle114onMounted(() => {115const handler = (e) => { matches.value = e.matches }116mediaQuery.addEventListener('change', handler)117onUnmounted(() => mediaQuery.removeEventListener('change', handler))118})119}120121return matches122}123```124125## Nuxt.js Guards126127```vue128<script setup>129// Nuxt provides process.client and process.server130if (process.client) {131// Only runs in browser132window.analytics.track('page_view')133}134135if (process.server) {136// Only runs on server137console.log('Rendering on server')138}139</script>140```141142```vue143<template>144<!-- ClientOnly component for client-only rendering -->145<ClientOnly>146<BrowserOnlyChart :data="chartData" />147<template #fallback>148<ChartSkeleton />149</template>150</ClientOnly>151</template>152```153154## Cross-Platform Libraries155156Use libraries that abstract platform differences:157158```javascript159// Fetch - works in both Node.js 18+ and browsers160const response = await fetch('/api/data')161162// For older Node.js, use node-fetch or axios163import axios from 'axios'164const { data } = await axios.get('/api/data')165```166167```javascript168// Universal cookie handling169import Cookies from 'js-cookie' // Client only170import { parse } from 'cookie' // Works both171172// In Nuxt, use useCookie()173const token = useCookie('auth-token')174```175176## Common Node.js APIs That Break in Browser177178| API | Browser Behavior |179|-----|-----------------|180| `fs` | Module not found |181| `path` | Module not found |182| `process` (full) | Undefined or limited |183| `Buffer` | Undefined (unless polyfilled) |184| `__dirname` / `__filename` | Undefined |185| `require()` | Undefined in ES modules |186187**Incorrect:**188```javascript189// WRONG: Node.js APIs in universal code190import fs from 'fs'191const config = JSON.parse(fs.readFileSync('./config.json'))192```193194**Correct - Separate Server Code:**195```javascript196// server/utils.js - Server-only file197import fs from 'fs'198export function loadConfig() {199return JSON.parse(fs.readFileSync('./config.json'))200}201202// app.js - Universal code uses API instead203const config = await fetch('/api/config').then(r => r.json())204```205206## Environment Detection Utility207208```javascript209// utils/environment.js210export const isClient = typeof window !== 'undefined'211export const isServer = !isClient212213export const isBrowser = isClient && typeof document !== 'undefined'214export const isNode = typeof process !== 'undefined' &&215process.versions?.node != null216217// Usage218import { isClient, isServer } from '@/utils/environment'219220if (isClient) {221// Browser-specific code222}223```224225## Third-Party Library Issues226227Some libraries auto-access browser APIs on import:228229```javascript230// WRONG: Library accesses window on import231import SomeChartLibrary from 'some-chart-library'232// ^ Crashes on server if library does: const x = window.something233```234235**Correct - Dynamic Import:**236```vue237<script setup>238import { defineAsyncComponent } from 'vue'239240// Dynamic import only loads on client241const Chart = defineAsyncComponent(() =>242import('some-chart-library').then(m => m.ChartComponent)243)244</script>245246<template>247<ClientOnly>248<Chart :data="data" />249</ClientOnly>250</template>251```252253## Reference254- [Vue.js SSR - Platform-Specific APIs](https://vuejs.org/guide/scaling-up/ssr.html#access-to-platform-specific-apis)255- [Nuxt ClientOnly Component](https://nuxt.com/docs/api/components/client-only)256- [MDN: Web APIs](https://developer.mozilla.org/en-US/docs/Web/API)257