Open-Source Wikis

/

Vue.js

/

Features

/

Server-side rendering

vuejs/core

Server-side rendering

Vue's SSR splits across three packages:

  • @vue/compiler-ssr generates SSR-flavored render functions that push string fragments instead of vnodes. See packages/compiler-ssr.
  • @vue/server-renderer drives those render functions, manages the SSR context, and exposes string + stream APIs. See packages/server-renderer.
  • @vue/runtime-core is reused for component instance creation, props/slots normalization, async dependency tracking, and built-in component logic. The internal ssrUtils export (gated by __SSR__) is the curated entry point for the server renderer.

This page describes how a render goes from renderToString(app) to a finished HTML string.

The SSR render

sequenceDiagram
    participant Caller as renderToString(app)
    participant SR as server-renderer
    participant RC as runtime-core ssrUtils
    participant Comp as ComponentInternalInstance
    participant SSRFn as ssrRender (generated)
    Caller->>SR: renderToString(app)
    SR->>SR: createApp().mount('<placeholder>') in SSR mode
    SR->>RC: createComponentInstance + setupComponent
    RC->>Comp: run setup() (may await)
    SR->>Comp: pull instance.ssrRender
    SR->>SSRFn: ssrRender(_ctx, _push, _parent, _attrs)
    SSRFn->>SR: _push("<div ...>") and recurse
    SR-->>Caller: resolved string

The renderer's primary loop is in packages/server-renderer/src/render.ts. For each component:

  1. createComponentInstance(vnode, parent, suspense) is called via ssrUtils.createComponentInstance.
  2. setupComponent(instance, /* isSSR */ true) runs setup() and applies Options API normalization. If setup() is async (returns a Promise or uses top-level await), the renderer awaits before continuing.
  3. The component's ssrRender (precompiled by @vue/compiler-ssr) is invoked with (ctx, _push, _parent, _attrs). _push writes string chunks to the active buffer.

If a component lacks a precompiled ssrRender (e.g., it has only a runtime render), the server renderer falls back to invoking render to get a vnode tree, then walks the vnodes with the same SSR helpers. This is slower but keeps semantics consistent.

What compiler-ssr emits

A simple template:

<template>
  <div :class="cls">{{ msg }}</div>
</template>

Compiles (via @vue/compiler-ssr.compile with mode: 'module') to roughly:

import { ssrInterpolate, ssrRenderClass } from '@vue/server-renderer';

export function ssrRender(_ctx, _push, _parent, _attrs) {
  _push(
    `<div class="${ssrRenderClass(_ctx.cls)}">${ssrInterpolate(_ctx.msg)}</div>`
  );
}

Calls to ssrRenderAttrs, ssrRenderClass, ssrRenderStyle, ssrInterpolate, ssrRenderComponent, ssrRenderSlot, ssrRenderTeleport, ssrRenderSuspense, etc., are all defined in packages/server-renderer/src/helpers/. The names listed in packages/compiler-ssr/src/runtimeHelpers.ts must match these exports — that's how the compiler/runtime stay in sync.

SSR context

The user passes (or the server renderer creates) an SSRContext object. It carries:

  • _teleports — a record of target-selector → rendered-string populated by ssrRenderTeleport. The server caller can splice these into the right places in the final HTML envelope.
  • Module assignments from <script> modules.
  • CSS variables collected by useCssVars.

useSSRContext() (re-exported from runtime-core) is how a component reads the context.

Streams

renderToString buffers the full output before resolving. For large pages, the stream variants in packages/server-renderer/src/renderToStream.ts produce content incrementally:

  • renderToSimpleStream(input, ctx, stream) — lowest-level. Accepts a SimpleReadable ({ push(chunk), destroy(reason?) }).
  • renderToNodeStream(input, ctx?) — Node-style Readable adapter.
  • pipeToNodeWritable(input, ctx, writable) — pumps chunks into a Writable.
  • renderToWebStream(input, ctx?) — Web ReadableStream adapter.
  • pipeToWebWritable(input, ctx, writable) — Web WritableStream adapter.

Internally, all variants share a buffer model where each component appends a Buffer (a recursive list of strings or Promise entries). Async dependencies (async components, top-level await in <script setup>, <Suspense> async children) become Promise nodes; the streamer flushes chunks in tree order, awaiting promises as it encounters them.

Async components and Suspense

defineAsyncComponent returns a wrapper that resolves to the real component on first use. In SSR:

  • The wrapper's pending promise is awaited before its render runs.
  • <Suspense> collects pending dependencies of its default slot. If any are still pending after a timeout (or the suspense itself resolves synchronously), the fallback slot is rendered instead.

packages/server-renderer/src/helpers/ssrRenderSuspense.ts is the SSR-side handler. packages/runtime-core/src/components/Suspense.ts provides the boundary type that the SSR code consults.

Hydration mismatch

The expected client-side hydration consumes the SSR-produced markup verbatim. Mismatches surface in dev as warnings via packages/runtime-core/src/hydration.ts. Common sources of mismatches:

  • Markup that depends on Date.now() or Math.random() (different on each side).
  • Branches gated by typeof window.
  • Interpolating user-provided HTML differently in SSR vs client.
  • Whitespace differences when whitespace: 'preserve' is not aligned across compile passes.

The flag __VUE_PROD_HYDRATION_MISMATCH_DETAILS__ enables detailed mismatch reports in production builds at the cost of bundle size.

Directives in SSR

v-show and v-model need SSR-aware behavior so the server-rendered markup matches what the client will hydrate. runtime-dom/src/index.ts exports initDirectivesForSSR; server-renderer/src/index.ts calls it on import. The actual SSR transforms live in packages/compiler-ssr/src/transforms/ssrVModel.ts and ssrVShow.ts, but the runtime initializers in runtime-dom/src/directives/vModel.ts and vShow.ts provide the matching getSSRProps hook the SSR pipeline calls.

Files to know

  • packages/server-renderer/src/render.ts
  • packages/server-renderer/src/renderToString.ts
  • packages/server-renderer/src/renderToStream.ts
  • packages/server-renderer/src/helpers/ssrRenderAttrs.ts
  • packages/server-renderer/src/helpers/ssrRenderComponent.ts
  • packages/server-renderer/src/helpers/ssrRenderTeleport.ts
  • packages/server-renderer/src/helpers/ssrRenderSuspense.ts
  • packages/compiler-ssr/src/index.ts
  • packages/compiler-ssr/src/transforms/ssrTransformElement.ts
  • packages/compiler-ssr/src/transforms/ssrTransformComponent.ts
  • packages/runtime-core/src/index.ts (look for ssrUtils)
  • packages/runtime-core/src/components/Suspense.ts

Companion: hydration

For the client-side counterpart of SSR — taking the produced markup and binding it to the live component tree — see hydration.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Server-side rendering – Vue.js wiki | Factory