Would you hand-roll SSR on Vue core's createSSRApp and vue/server-renderer, or adopt a Vue meta-framework, and what decides it?
answer
- what renderToString leaves to you
- two builds and asset links
- routing, data, state transfer
- ownership versus convention
basics
~20 sVue core provides rendering and hydration only; builds, asset links, routing, data loading, state transfer, head tags and error pages are yours. Vue's docs recommend a framework for most SSR apps; hand-rolling suits small, contained cases needing control.
solid answer
~50 sVue core supplies two pieces: `createSSRApp` for a universal app that hydrates, and `vue/server-renderer` to produce HTML as a string or stream. Everything else is yours: coordinating a client and a server build, linking the right assets and preload hints from the shell, per-request apps and routers, loading data on the server and serializing it safely, head management through the SSR context, teleports, status codes and error pages, a development server, and choosing SSR or prerendering per route. The Vue SSR guide says a complete implementation is quite complex and highly recommends a framework with built-in SSR. I would hand-roll when the scope is small and fixed: a few routes, SSR embedded in an existing server, an unusual runtime, or a need to control every byte, and adopt a framework once the app has many routes, a team, and SEO or data requirements that grow.
go deeper
Know that Vue core provides createSSRApp and the server renderer, and that Vue's docs recommend a framework for most SSR needs.
List what a hand-rolled setup must build beyond renderToString: two builds, asset links, routing, state transfer and error pages.
Estimate the maintenance cost of each piece and spot the signals that a hand-rolled server has outgrown its design.
Make the call from requirements, team shape and hosting, and state the trade: owning every layer versus inheriting a framework's conventions and upgrade path.
## What Vue core actually gives you Vue core's SSR surface is deliberately small: - `createSSRApp`: an app whose `mount()` hydrates server-rendered DOM. - `vue/server-renderer`: `renderToString`, the Node and web stream renderers, and the SSR context with `useSSRContext` and `ctx.teleports`. - SSR-aware component behaviour: `onServerPrefetch`, reactivity disabled on the server, `getSSRProps` for directives. That is enough to render an app to HTML and hydrate it. It is not an application platform. ## What you own when you hand-roll The Vue SSR guide lists what moving from its minimal example to production involves, and practice adds more: 1. **Two builds**: a client bundle and a server bundle of the same code, with templates compiled for string output on the server. 2. **Asset linking**: the server shell must reference the right hashed client files and emit resource hints. 3. **Routing**: create a router per request, resolve the URL, wait for async route components, and map misses to a 404. 4. **Data and state**: load data on the server, serialize it with escaping, restore it on the client before hydration, and avoid cross-request state. 5. **Head and status**: gather titles, meta tags and status decisions through the SSR context. 6. **Errors**: decide which component errors fail the page, and render error pages. 7. **Rendering modes**: pick string or stream per route, and possibly prerender some routes instead of rendering them per request. 8. **Developer experience**: a dev server that renders on the server with hot updates. The guide's own conclusion is that a complete implementation is quite complex, depends on the build toolchain, and that a Vue framework with built-in SSR is highly recommended when you need SSR. ## The decision | Factor | Points to hand-rolling | Points to a framework | |---|---|---| | Scope | a few routes, a widget, a known set of pages | many routes, growing product | | Hosting | SSR inside an existing server or an unusual runtime | a standard deployment target | | Team | one or two people who know the internals | several teams needing shared conventions | | Control | every byte, header and chunk matters | conventions are acceptable | | Maintenance | willing to own build and upgrade glue | prefer to upgrade one dependency | Neither side is free: - **Hand-rolling** costs build, routing and state-transfer code that the team must maintain and debug, and it concentrates knowledge in few people. - **A framework** couples you to its conventions, release cadence and abstractions; unusual requirements can mean fighting it. ## How I would answer in an interview - Start from requirements: SEO or first-paint needs, number of routes, data sources, hosting constraints. - If SSR is needed for a small, stable surface, hand-rolling on `createSSRApp` plus `renderToString` is reasonable and teaches the team exactly what runs where. - If the app is a growing product, adopt a framework early, because retrofitting per-request routing, state transfer and asset linking into a hand-rolled server is costly. - Either way, the Vue-level rules still apply: universal code, an app per request, SSR-safe components and matching server and client renders. ## Questions to settle before deciding 1. Does the page need per-request rendering at all, or would prerendering at build time serve it? 2. How many routes will need server data a year from now? 3. Where will it run, and does that runtime offer Node streams, web streams or neither? 4. Who owns the server entry and build glue when its author moves on? 5. Which framework constraints would the product hit, and can a short spike test them first? A time-boxed spike on the riskiest requirement usually settles more than a debate does. ## Signals that a hand-rolled setup has outgrown itself - Every new route needs changes in the server entry. - State-transfer and head-management code is duplicated per page. - Build and dev-server scripts break on bundler upgrades. - Only one person can debug a hydration or streaming issue.
- What is the smallest Vue SSR setup you would still consider production-worthy when hand-rolling?A universal factory using `createSSRApp` that creates the app and state per request; a server entry that renders with `renderToString`, escapes and serializes state, links hashed client assets and sends a real error page on failure; a client entry that restores state before `mount()`; and separate client and server builds. Anything less leaks state, breaks hydration or ships stale assets.
- You start hand-rolled and later need a framework. What in your Vue code carries over?Components written as universal, SSR-safe code carry over: no browser APIs in setup, data loaded through server-aware hooks, state injected rather than imported from module scope. The server entry, build wiring and state-transfer glue are what get replaced, so keeping them thin and separate from components makes the move cheaper.
saying these in an interview costs you the question
- Vue core's renderToString handles routing and data loading for you.
- The Vue docs recommend hand-rolling SSR for most production apps.
- A framework removes the need to write SSR-safe components.
- Hand-rolled SSR needs only one build shared by server and browser.
- Choosing a framework is purely about performance, not maintenance.