Vue docs, chapter by chapter

Scaling up — SFC, tooling, routing, state, testing, SSR

What the guide says about single-file components and their CSS features, the recommended toolchain, routing and state management at the Vue level, the testing pyramid it proposes, and the SSR chapter's rules for SSR-friendly code.

The Scaling Up chapters are the guide's opinions: which tools to use, how to think about state, how to test, and the rules for code that must run on a server. Nuxt implements most of this for you, which is exactly why interviewers ask about it: they want to know whether you understand what the framework is doing on your behalf.

Know — Single-File Components

  • A .vue file holds <template>, <script> (or <script setup>), <style> and optional custom blocks; @vue/compiler-sfc compiles it to standard JavaScript and CSS at build time.
  • Why SFCs: one file per component with familiar syntax, pre-compiled templates (no runtime compiler), component-scoped CSS, better IDE support, HMR out of the box. They require a build step, so the guide recommends them whenever one exists.
  • CSS features: <style scoped> adds a data-v-hash attribute selector; a child component's root receives the parent's scoped styles; :deep() targets child internals, :slotted() slot content, :global() escapes scoping; mixing scoped and global blocks is allowed. CSS modules via <style module> expose $style (or a custom name), and useCssModule() in setup. v-bind() in CSS binds a reactive value to a custom property (color: v-bind('theme.color')).
  • Language blocks: lang="ts", lang="scss", lang="pug"; src imports for any block; automatic component name inference from the filename.

Know — Tooling

  • Scaffold with npm create vue@latest (create-vue, Vite-based); Vue CLI is in maintenance mode. Online: SFC Playground, StackBlitz.
  • IDE: VS Code + Vue (Official) extension (formerly Volar), WebStorm built in. Type checking on the command line with vue-tsc. Browser devtools: the Vue Devtools extension (also standalone and as a Vite plugin).
  • Build integration: @vitejs/plugin-vue for Vite, vue-loader for webpack. Linting with eslint-plugin-vue, formatting with Prettier (or the extension's formatter). Lower-level: @vue/compiler-sfc for custom tooling.

Know — Routing and State Management

  • Client-side routing means intercepting navigation and swapping components without a full page load; the official solution is Vue Router. The guide shows a from-scratch version with <component :is> and hashchange.
  • State management in the guide's words: a one-way flow of state → view → actions. The simple store is a reactive() object exported from a module, but that is a singleton shared across requests in SSR, which is the "cross-request state pollution" the SSR chapter warns about.
  • Pinia is the official store: devtools, HMR, plugins, TypeScript, SSR support; Vuex 4 is in maintenance mode for Vue 3.

Know — Testing

  • Unit tests for logic (composables, utilities) with Vitest (Vite-native); component tests with Vue Test Utils (low level, official) or Testing Library (user-centric), also Cypress component testing; end-to-end with Playwright, Cypress, Nightwatch or WebdriverIO.
  • Component testing advice: test the public interface, not implementation (inputs → props, user interaction, outputs → rendered DOM, emitted events); avoid asserting on internal state; snapshots only with care.
  • Recipes: vitest with environment: 'happy-dom' or 'jsdom'; testing composables with a withSetup helper that mounts a throwaway app so lifecycle hooks and inject work, then unmounts it.

Know — Server-Side Rendering

  • What: render the app to HTML on the server, then hydrate in the browser by attaching Vue to the existing DOM. Why: faster time-to-content, one mental model for both sides, better SEO. Trade-offs: browser-only code needs care, a Node build/deploy setup, server CPU load; SSG (pre-rendering at build) fits static content.
  • Basic tutorial: createSSRApp on both sides, renderToString from vue/server-renderer on the server, .mount() on the client to hydrate; import.meta.env.SSR in Vite tells you where code runs. The guide points to Nuxt, Quasar and Vite SSR as higher-level solutions.
  • SSR-friendly code: on the server only beforeCreate/created (or setup) run, no mounted; guard platform APIs; avoid module singletons for state (create the app, router and store per request); use app.runWithContext when calling composables outside setup.
  • Hydration mismatches come from invalid HTML nesting (<div> inside <p>), non-deterministic values (Date.now(), Math.random()), timezone differences, and browser extensions editing the DOM; data-allow-mismatch (3.5) suppresses an expected one. Custom directives need getSSRProps; Teleports render into ctx.teleports and must be injected into the HTML manually.

How it works

The guide's withSetup helper, which is the standard way to unit-test a composable:

test/with-setup.ts
import { createApp } from 'vue'

export function withSetup<T>(composable: () => T): [T, ReturnType<typeof createApp>] {
  let result!: T
  const app = createApp({
    setup() {
      result = composable()
      return () => {} // render nothing
    },
  })
  app.mount(document.createElement('div'))
  return [result, app] // call app.unmount() at the end of the test
}

The SFC CSS features in one block:

app/components/Docs/Panel.vue
<script setup lang="ts">
const theme = reactive({ color: 'tomato' })
const styles = useCssModule() // $style in the template
</script>

<template>
  <section :class="styles.panel">
    <ChildCard class="card"><slot /></ChildCard>
  </section>
</template>

<style scoped>
.card :deep(.title) { font-weight: 600; }   /* child internals */
:slotted(p) { margin: 0; }                 /* slot content */
:global(body) { margin: 0; }               /* escape scoping */
section { color: v-bind('theme.color'); }  /* reactive custom property */
</style>

<style module>
.panel { padding: 1rem; }
</style>

Cross-request state pollution, the SSR chapter's central warning:

// ✗ store.ts — one object for the whole server process
export const store = reactive({ user: null })

// ✓ create per request and provide it
export function createStore() { return reactive({ user: null }) }
// server entry: const app = createSSRApp(App); app.provide('store', createStore())
Gotcha· Scoped styles reach the child's root

<style scoped> in a parent still applies to the root element of a child component, by design, so layout styling works. Everything below the child's root needs :deep(). People discover this when a child's root class collides with a parent's scoped selector.

Exercise

Exercise
  • Use withSetup to test a composable that registers onMounted and injects a value; confirm both work and the test fails without the helper.
  • Write a scoped style that accidentally styles a child's root, then a :deep() rule for its internals; inspect the generated selectors in devtools.
  • Render a component with renderToString in a Node script twice with different data using a module-level store, and observe the pollution.

Be able to say

Be able to say· What does the Vue guide say about writing SSR-friendly code?

"Three rules. Only setup, beforeCreate and created run on the server, so anything touching the DOM or browser APIs goes into mounted or behind an environment check. Never keep state in module-level singletons, because one Node process serves many requests and a shared reactive() leaks one user's data into another's render; create the app, router and store per request, which is what Pinia and Nuxt do for you. And produce deterministic markup: valid HTML nesting, no Date.now() or Math.random() in the render, or a data-allow-mismatch when a difference is intended. Everything else, hydration and renderToString, the framework handles."

Your notes

Open in Notes

Nothing yet. Notes filed here show up on this page and the section overview.