Isomorphic Rendering with Vue 3: Concepts, Implementation, and Common Pitfalls
This article explains the fundamentals of client‑side rendering (CSR), server‑side rendering (SSR), and isomorphic rendering, demonstrates a minimal Vue 3 isomorphic example with code snippets, discusses dehydration/hydration, and shares practical tips and pitfalls for building production‑grade SSR applications.
1. What is Isomorphic Rendering? Why Use It?
Isomorphic rendering combines the advantages of CSR and SSR to deliver fast first‑page loads, good SEO, and smooth client interactions.
1.1 What is Rendering?
Rendering converts JSX or Vue templates into HTML that browsers can display.
1.2 What is Client‑Side Rendering (CSR)?
CSR is the default mode where the server returns an empty HTML page and a bundled JavaScript file that renders the UI in the browser, often causing a white‑screen delay and poor SEO.
CSR Advantages and Disadvantages
CSR bundles the whole app into JavaScript, enabling fast navigation after the initial download, but it suffers from initial white‑screen delays and limited SEO.
1.3 What is Server‑Side Rendering (SSR)?
SSR generates the full HTML on the server (e.g., using JSP) before sending it to the client, eliminating white‑screen issues and improving SEO, at the cost of higher server load and slower navigation.
1.4 What is Isomorphic Rendering?
Isomorphic rendering first renders the page on the server (SSR) and then sends the same JavaScript bundle to the client for subsequent navigation, achieving both fast first paint and smooth client‑side interactions.
1.5 CSR, SSR, and Isomorphic Comparison
CSR
SSR
Isomorphic
SEO
Unfriendly
Friendly
Friendly
White‑screen
Yes
No
No
Server resource usage
Low
High
Medium
User experience
Good
Poor
Good
2. A Minimal Isomorphic Example
The following code shows how to render a simple Vue counter component to a string on the server and send it to the client.
import { renderToString } from 'vue/server-renderer'
import { createSSRApp } from 'vue'
function createApp() {
return createSSRApp({
data: () => ({ count: 1 }),
template: `<button @click="count++">{{ count }}</button>`
})
}
const app = createApp()
renderToString(app).then(html => {
const htmlStr = `
<!DOCTYPE html>
<html>
<head><title>Vue SSR Example</title></head>
<body>
<div id="app">${html}</div>
</body>
</html>
`
console.log(htmlStr)
})Run the file with node xxx.js to see the rendered HTML.
2.1 Server‑Side Rendering the HTML String
Use renderToString to obtain the HTML and embed it in a full page template.
2.2 Sending the HTML via Express
import express from 'express'
import { renderToString } from 'vue/server-renderer'
import { createSSRApp } from 'vue'
function createApp() {
return createSSRApp({
data: () => ({ count: 1 }),
template: `<button @click="count++">{{ count }}</button>`
})
}
const server = express()
server.get('/', (req, res) => {
const app = createApp()
renderToString(app).then(html => {
const htmlStr = `
<!DOCTYPE html>
<html>
<head><title>Vue SSR Example</title></head>
<body>
<div id="app">${html}</div>
</body>
</html>
`
res.send(htmlStr)
})
})
server.listen(3000, () => console.log('ready http://localhost:3000'))Visit http://localhost:3000/ to see the rendered page.
2.3 Activating Client‑Side Rendering
Because the server‑rendered HTML is static, add a client entry that mounts the app:
import { createSSRApp } from 'vue'
function createApp() {
return createSSRApp({
data: () => ({ count: 1 }),
template: `<button @click="count++">{{ count }}</button>`
})
}
createApp().mount('#app')Include this script in the HTML template with an importmap for Vue.
3. Dehydrate and Hydrate
Dehydration sends server‑fetched data inside a <script> tag (e.g., window.__INITIAL_DATA__); hydration reads that data on the client to avoid a second request.
// Server side: embed data
const htmlStr = `
...
<script>window.__INITIAL_DATA__ = ${JSON.stringify(initData)}</script>
...
`Client side checks window.__INITIAL_DATA__ and uses it if present, otherwise fetches data.
async mounted() {
if (window.__INITIAL_DATA__) {
this.count = window.__INITIAL_DATA__
window.__INITIAL_DATA__ = undefined
return
}
this.count = await getSomeData()
}4. Common Pitfalls in Isomorphic Apps
4.1 Avoid Global Singletons
Each request must get a fresh app instance; shared globals cause state leakage.
4.2 Guard Against Platform‑Specific APIs
Do not use window or document in Node; wrap such code in lifecycle hooks that only run on the client.
4.3 Do Not Run Global Side‑Effects During SSR
Timers or other side‑effects started in SSR can leak memory on the server.
5. Building a Production‑Ready Isomorphic Application
Beyond the basic example, a real project needs toolchain integration (Vite, ESLint, TypeScript), routing (vue‑router with memory history), state management (Pinia), handling of teleport/portal elements, and preload resource generation.
5.1 Server‑Side Optimizations
Use clustering, memory‑usage monitoring, uncaught‑exception handling, heartbeat checks, and automatic worker restart to keep the Node server stable under load.
// Example of clustering
import cluster from 'cluster'
import os from 'os'
if (cluster.isMaster) {
const cpuCount = os.cpus().length
for (let i = 0; i < cpuCount; i++) {
cluster.fork()
}
} else {
// start actual server here
}Stress test with tools like ApacheBench to verify performance.
5.2 Monitoring and Recovery
Track memory with process.memoryUsage(), handle uncaughtException, and use heartbeat messages between master and workers to detect and replace stuck processes.
5.3 Example Heartbeat Logic
// Master sends ping
worker.send('ping')
// Worker replies
process.on('message', msg => {
if (msg === 'ping') process.send('pong')
})These techniques ensure the SSR service remains responsive in production.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
