[Doc/Tuto] Example of prerendering for vue.
See original GitHub issueHi,
I made a simple landing page with vue 3 and vuejs, and would like to prerender it for seo and performance (and usage with netlify form).
I was looking for an existing solution, but didn’t find anything, so I’ve dug in the builder of vite and made my own script. I tried to make it as simple as possible, and at the end it’s pretty straightforward, but I’ve spent few hours to understand vite and rollup build, and I think it can be of interest to some people (you can start from there for SSR too).
I share it in the issue to keep a record, but I think it can be good to add some documentation about prerendering and ssr with vite and vue3.
Please feel free to give me your feedbacks, so we can improve it.
Prerendering with vite and vue3
Update you main.js
file
// main.js
import { createApp as createClientApp, createSSRApp } from 'vue'
import './assets/main.css'
import App from './App.vue'
const inBrowser = typeof window !== 'undefined'
// We export a function that will create a standard app in dev, but a ssr app for production.
// To enable hydration on the client, use createSSRApp instead of createApp to create your app instance (separate imports allows hydration logic to be tree-shakeable for apps that do not require hydration).
export const createApp = () => (
process.env.NODE_ENV === 'production'
? createSSRApp(App)
: createClientApp(App)
)
// When in browser, we create and mount the app like before.
if (inBrowser) {
createApp().mount('#app')
}
Add a build.js
file
const { ssrBuild, build } = require('vite')
const { resolve } = require('path')
const { writeFileSync, rmdirSync } = require('fs')
const renderer = require('@vue/server-renderer')
const build = async () => {
const outDir = resolve(process.cwd(), 'dist')
const tmpDir = resolve(process.cwd(), 'dist/tmp')
// Classic client build. Output assets, and index.html. Returns chunks and generated html.
const clientResult = await build({ outDir })
// Build for server side (imports are replaced by requires, and no assets)
await ssrBuild({
outDir: tmpDir,
// Important to keep the export createApp in the main.js.
rollupInputOptions: {
input: { index: 'src/main.js' },
preserveEntrySignatures: 'allow-extension',
},
})
// We import our main.js createApp from the new server flavored built file.
const { createApp } = require(tmpDir)
// Render the html of the app, and insert it in the generated index.html built for client side.
const content = await renderer.renderToString(createApp())
const indexPath = resolve(outDir, 'index.html')
const indexOutput = clientResult.html.replace(
'<div id="app">',
`<div id="app" data-server-rendered="true">${content}`,
)
// Write the new file and remove the server build, we don't need it anymore.
writeFileSync(indexPath, indexOutput)
rmdirSync(tmpDir, { recursive: true })
console.log('🎉 Page generated!')
process.exit()
}
build().catch((e) => {
console.error(e)
process.exit(1)
})
Update you build
script.
- "build": "cross-env NODE_ENV=production vite build"
+ "build": "cross-env NODE_ENV=production node build.js"
Have fun! 🎉 (and please share me your thoughts!)
Moreover, I’ve only one file here, but next step could be to generate several pages depending of routes.
Issue Analytics
- State:
- Created 3 years ago
- Reactions:4
- Comments:10 (9 by maintainers)
Top GitHub Comments
It’s for a next-gen framework, Factor 7
You don’t think that it’s fundamentally important to match your development and production environments?
Hydration and pre-fetching are error-prone enough where I don’t feel comfortable developing without SSR.