Resources
Current Development Version
"vue": "^2.6.11", "vue-router": "^3.3.4", "vue-server-renderer": "^2.6.11", "vuex": "^3.4.0"
concept
Server-side rendering: Render the vue instance as an HTML string directly back, activate as an interactive program on the front, old-fashioned; SSH can only return HTML strings and cannot activate.
The next step is to develop isomorphic (vue-like) rendering on the server side.
Current way of rendering on the server side
Traditional web development:
In traditional web development, web content is rendered directly on the server and transferred to the browser at one time.Get the html code directly from the database;
The disadvantage is: long server response time; bandwidth consumption, heavy load.This is what you see is what you get.
Single Page App: (spa)
Single use of excellent user experience, making it gradually mainstream, face content rendered by JS, this way is called client-side rendering. The structure of the html is returned to the front end, but there is no content.Content is rendered HTML by the front-end library, vue or react; then an ajax request is sent requesting data to get data from the data to render.
Disadvantages: Not good for SEO, not good for search engines.If the data is not returned, the first screen will load slowly
Server Side Render Rendering
SSR solutions, back-end rendering returns the full first-screen dom structure or is developed using the vue or react template.What the front end gets includes the first screen (html structure) and the full spa structure (routing....) to make a route jump on the front end; the application still runs in spa mode after activation, which is called server side render.
Advantages of using SSR
- seo: Search Engine Optimization
- The first screen rendering content arrives at the time of one request for a response;
If a spa requests a response once, and an ajax requests data after opening the request, the speed is not that fast.
express
npm i express -S
Basic http service code demo:
// nodejs code const express = require('express') // Here is an example of getting express. // You can see from the source code: //Source path: /node_modules/@types/express/index.d.ts // Declare function ():Core.Express; export = e is exported at the end; all executions of this function result in an instance const server = express() // Need to do routing, otherwise openHttp://localhost: 3000/port will error. // Write routes for different url processing // req request // res response server.get('/', (req,res)=>{ res.send('3000') //Browsers will think html is returned }) // Listening Port server.listen(3000, ()=>{ console.log('Executed') })
Enter the current directory and execute the file, such as node 1-express-start.js;
Showing executed means the code is OK
The same is true for direct access ports.localhost:300
The node file is modified and needs to be run once each time to install the nodemon; the NPM install-g nodemon can be updated in real time; and the nodemon 1-express can be used when starting the nodeStart.js
Basic server-side rendering
Use server to make vue instance into HTML HTML string and return
Vue server renderer: vue-server-renderer**
NPM I vue-server-renderer-S or install Vue NPM I vue-server-renderer-S at the same time; make sure the versions are the same
- After installation, test
Create the file first
There are three steps
1. Create a vue instance 2. Get a renderer instance 3. Render vue instances with a renderer
// Create vue instance const Vue = require ('vue') const app = new Vue({ template:'<div>Hello</div>' }) // Get a renderer instance const {createRenderer} = require('vue-server-renderer') // Get the factory function const renderer =createRenderer() // You get a renderer // Render vue instance with renderer // Returns promise, requiring. renderer.renderToString(app) .then((html)=>{ console.log(html) }) .catch((err)=>{ console.log(err); })
Results display:
data-server-rendered server-side rendered
What you need to do now is if the user refreshes the display and combines express
In the express get just now, just return the code written by vue above
// nodejs code const express = require('express') const server = express() // Create vue instance const Vue = require ('vue') // Get a renderer instance const {createRenderer} = require('vue-server-renderer') // Get the factory function // Render vue instance with renderer const renderer =createRenderer() // You get a renderer server.get('/', (req,res)=>{ // Render a new vue every time the user refreshes const app = new Vue({ template:'<div>Hello~~~~Wow~~~</div>' }) // Returns promise, requiring. renderer.renderToString(app) .then((html)=>{ // Return results directly to the browser res.send(html) }) .catch(()=>{ // Return status on error 500 res.status(500) res.send('Internal Server Error, 500') }) }) // Listening Port server.listen(3000, ()=>{ console.log('Executed') })
Modify it to use data display
const app = new Vue({ template:'<div>{{context}}</div>', data(){ return { context:'vvvvvue' } }
How do I interact?
Can I do this if I write directly on the server side?
template:'<div @click="onClick">{{context}}</div>', data(){ return { context:'vue-ssr' } }, methods: { onClick(){ console.log('Can I click on it?') } }
Page:
Binding did not succeed because:
It is not possible to convert to a string and send it to the front end.So you need to activate the process
Solve Routing, Homogeneous Problems
Route
npm i vue-router -s
In the absence of an SSR, a Router instance of an instance is returned.
In the case of server-side rendering, to avoid the problem of Router pollution, each request returns a new Router.
//As a factory function, a new router instance is returned each time a user requests it export default function createRouter(){ return new VueRouter({ mode: 'history', base: process.env.BASE_URL, routes }) }
How can I get the route rendered by the server to the front end?
You need to understand the build process first:
Or do you need to package with a webpack?
There are two service-side entries: Server entry, Client enrty
Two packages will be generated - -------
Generate file: Server Bundle "Server bundle" is used for rendering the first screen of the service side (not the first page, what page is requested or what is the first screen), Client Bundle "Client bundle" is used for client activation (generated js code is attached to html, create a new vue instance, such as the implementation of the click event tested above) because the service side is passing in a string.Front end needs to be activated.
Code structure
src ├── router ├────── index.js # Route ├── store ├────── index.js # Global state ├── main.js # Create vue instance ├── entry-client.js # Client Entry, Static Content Activated └── entry-server.js # Server Entry, First Screen Content Rendering
- Main.jsAdjustments
src/main.js
import Vue from 'vue' import App from './App.vue' import createRouter from './router' Vue.config.productionTip = false // Get a separate vue instance per request // `The caller is an entry-server (first screen rendering) which passes parameters as context objects` export function createApp(context){ const router = createRouter() const app =new Vue({ router, context, // You can get some parameters with context render: h => h(App) }).$mount('#app') // Export app and router instances return {app, router} }
- Entry-Server.jsFirst Screen Rendering
src/entry-server.js
`// First Screen Rendering` `// Code executed on the server side` import {createApp} from './main' // Create an instance of vue `// The caller is renderer` export default context =>{ // In order for renderer to wait for the final result to be processed, the return ed should be a promiss return new Promise((resolve, reject)=>{ // Create vue instance and routing instance const {app, router} =createApp(context) // To render the first screen, get the current url The renderer gets the current url // Jump to the first screen. // The source of the url.Is available from the request.Pass to renderer router.push(context.url) // Consider asynchronous task processing such as ajax requests on the current page.To wait for the asynchronous task to finish processing on the jump page // Listen for router ready, really asynchronous task completed router.onReady(()=>{ //This method queues a callback and calls it when the route completes its initial navigation, which means it can resolve all asynchronous incoming hooks and the asynchronous components associated with route initialization. //This can effectively ensure that both server and client outputs are consistent when rendering on the server side. resolve(app) }, reject) // Failure function handling as onReady event }) }
- Entry-Client.jsClient Activation
src/entry-client.js
`// Client activation is client interaction such as click // Code Executed in Browser` import {createApp}~~~~ from './main' // Create vue instance const {app, router} =createApp() //Wait for router to be ready router.onReady(()=>{ //Mount Activation app.$mount('#app') })
Both entry-server and entry-client are usedMain.jsThe createApp rendering instance in must have an instance of vue;
webpack packaging
- Installation Dependency
npm install webpack-node-externals lodash.merge \-D
- Create aVue.config.jsIn the root directory
// Two plug-ins are responsible for packaging the client and the server const VueSSRServerPlugin = require("vue-server-renderer/server-plugin"); const VueSSRClientPlugin = require("vue-server-renderer/client-plugin"); const nodeExternals = require("webpack-node-externals"); const merge = require("lodash.merge"); // Determine entry files and corresponding configuration items based on incoming environment variables const TARGET_NODE = process.env.WEBPACK_TARGET === "node"; const target = TARGET_NODE ? "server" : "client"; module.exports = { css: { extract: false }, outputDir: './dist/'+target, // Output path target see above judgment configureWebpack: () => ({ // enter path // Point entry to the application's server / client file entry: `./src/entry-${target}.js`, // Entrance // Provide source map support for bundle renderer devtool: 'source-map', // target is set to node so that the webpack handles dynamic imports in the way Node applies. // It also tells `vue-loader` to output server-oriented code when compiling a Vue component. target: TARGET_NODE ? "node" : "web", // Whether to simulate node global variables node: TARGET_NODE ? undefined : false, output: { // Export module using Node style here libraryTarget: TARGET_NODE ? "commonjs2" : undefined }, // https://webpack.js.org/configuration/externals/#function // https://github.com/liady/webpack-node-externals // Externalized applications depend on modules.This allows the server to build faster and generate smaller packaged files. externals: TARGET_NODE ? nodeExternals({ // Do not externalize the dependent modules that the webpack needs to handle. // You can add more file types here.For example, the original *.vue file was not processed. // Dependency modules that modify `global'(such as polyfill) should also be whitelist whitelist: [/\.css$/] }) : undefined, optimization: { splitChunks: undefined }, // This is a plug-in that builds the entire output of the server into a single JSON file. // Server-side default file name is `vue-ssr-server-bundle.json` // Client default file name is `vue-ssr-client-manifest.json`. plugins: [TARGET_NODE ? new VueSSRServerPlugin() : new VueSSRClientPlugin()] }), chainWebpack: config => { // cli4 Project Add if (TARGET_NODE) { config.optimization.delete('splitChunks') } config.module .rule("vue") .use("vue-loader") .tap(options => { merge(options, { optimizeSSR: false }); }); } };
Package.jsonAdjust
- Installation Dependency:
npm i cross-env \-D
"scripts": { "build": "npm run build:server & npm run build:client", "build:client": "vue-cli-service build", "build:server": "cross-env WEBPACK_TARGET=node vue-cli-service build" },
Execute npm run build for packaging
- dist file
- Index.htmlHost File
public/index.html
The format of the comment is agreed upon. Do not add spaces
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> </head> <body> <!--vue-ssr-outlet--> </body> </html>
- Adjust the node file in the server
server/4-ssr.js
const express = require('express') const app = express() // Static Resource Service // Open this path (.. /dist/client) so that users can download files const path = require('path') const resolve = dir => path.resolve(__dirname, dir) // Relative paths are unreliable (.. /dist/client), absolute paths are required app.use(express.static(resolve('../dist/client'),{index: false}))//Specify the root directory and develop it for users to see //The purpose of the {index: false} setting is because there is aIndex.html; so instead of going through the code below, you just go back to the client index.html // Renderer: bundleRenderer, which gets the two json files generated earlier const { createBundleRenderer } = require('vue-server-renderer') //Point to absolute path const bundle = resolve('../dist/server/vue-ssr-server-bundle.json') //Get a renderer that can directly render vue instances const renderer = createBundleRenderer(bundle, { // option runInNewContext: false, // Https://ssr.vuejs.org/zh/api/#runinnewcontextDocument Address // Host File template: require('fs').readFileSync(resolve("../public/index.html"), "utf-8"), // Convert host file utf-8 to string clientManifest: require(resolve("../dist/client/vue-ssr-client-manifest.json")) // Client Inventory Optimizations }) app.get('*',async(req,res)=>{ const context = { url: req.url } try{ // Rendering Get html // Create vue instance Create first screen rendering is now static and non-interactive const html = await renderer.renderToString(context) res.send(html)//Send to front-end interactive entry-client to render as soon as it hangs }catch(error){ res.status(500).send('Internal Server Error') } }) app.listen(3001)
- Code Test Results
Success!!!
(Note) To the current development progress, each project modification requires the npm run build and start node file again
Integrate vuex
Install vuex
`npm install -S vuex` If you are creating a project with vue-cli, you can use vue add vuex installation, results are the same
- Generate store/index.js
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) // `Need to be independent` export default function createStore(){ return new Vuex.Store({ state: { count:100 }, mutations: { add(state){ state.count+=1 } }, actions: { }, modules: { } }) }
- stayMain.jsHang in sotre
import Vue from 'vue' import App from './App.vue' import createRouter from './router' `import createStore from './store'` Vue.config.productionTip = false // Get a separate vue instance per request // Caller is entry-server (first screen rendering) passing parameter is context object export function createApp(context){ const router = createRouter() const store = createStore() const app =new Vue({ router, // You can get some parameters with context context, store, `// Mount` render: h => h(App) }).$mount('#app') // Export app and router instances return {app, router, store} }
- stay Hello.vue Add in
Test the import of vuex
<p @click="$store.commit('add')">{{$store.state.count}}</p>
Data prefetching
Server-side rendering is a "snapshot" of the application, and if the application relies on some asynchronous data, it needs to be prefetched and parsed before rendering can begin.
Prepare the data on the server side and render the page with the data.
- First in store/Index.jsInitialization of adding a data to
mutations: { // Add Initialization Data init(state,count){ state.count =count } },
- After that in sotre/Index.jsInitiate an asynchronous request using actions to mimic the request from an interface
actions: { `// An asynchronous request for data triggers init to mimic an interface` getCount({commit}){ return new Promise((reslove)=>{ setTimeout(() => { commit('init', Math.random()*100) // Generate random numbers as initial values reslove() }, 1000); }) } },
- Next, do the data prefetching logic on the routing page where you need to invoke the dataHello.vue
export default { name: 'Hello', asyncData({store, router}) { console.log(router, 'asyncDtata-router') return store.dispatch('getCount') } }
AsyncData allows you to get data asynchronously before rendering components.The asyncData method is called before each load of the component (page component only).It can be invoked on the server side or before routing updates.
- First screen processing data in entry-server.js
// First Screen Rendering //Code executed on the server side import {createApp} from './main' //Create an instance of vue // Caller is renderer export default context =>{ // In order for renderer to wait for the final result to be processed, the return ed should be a promiss return new Promise((resolve, reject)=>{ // Create vue instance and routing instance `const {app, router,store} =createApp(context)` // To render the first screen, get the current url The renderer gets the current url // Jump to the first screen. // The source of the url.Is available from the request.Pass to renderer router.push(context.url) // Consider asynchronous task processing such as ajax requests on the current page.To wait for the asynchronous task to finish processing on the jump page // Listen for router ready, really asynchronous task completed router.onReady(()=>{ //This method queues a callback and calls it when the route completes its initial navigation, which means it can resolve all asynchronous incoming hooks and the asynchronous components associated with route initialization. //This can effectively ensure that both server and client outputs are consistent when rendering on the server side. `// Processing asynchronous data first, then storing it in rendering // So you need to match whether the asyncData option exists in the build const matchedComponents =router.getMatchedComponents() // Get URLs that match all matching build arrays` //User is a mistyped address, which may cause matchedComponents to get Error 404 if(!matchedComponents.length){ return reject({code:404}) } `//You need to iterate through the array to see if the group matches asyncData` Promise.all( matchedComponents.map(comp =>{ // See if the build matches asyncData if(comp.asyncData){ // Pass store to find actions corresponding to dispatch // router is passed if url is followed by a parameter &wd=vue return comp.asyncData({store,route:router.currentRoute})// The asynchronous call therefore returns a promise, which returns an array of promises each time } })).then(()=>{ // Placing data on the front end of the store does not know this step, so you need to notify the front end //Next, make an appointment // After all prefetched data resolve s // store has been entered to populate the current data state //Data needs to be synchronized to the front end // Serialization operation, converted to string front end using window. uINITIAL_STATE_uObtain // Assign toContext.state; is a convention, context.state = store.state resolve(app) }).catch(reject) // Catch Exceptions }, reject) // Failure function handling as onReady event }) }
In entry-client.js
Restore store
const {app, router, store} =createApp() if(window.__INITIAL_STATE__){ console.log(window.__INITIAL_STATE__, 'window.__INITIAL_STATE__'); // Restore state store.replaceState(window.__INITIAL_STATE__) }
When rendered directly by the server, a string is generated directly after deserialization and inserted here, which becomes an object as soon as it is executed on the front end.
- About as the first page, refreshed and returned to home page, asyncData did not execute
If, the route switches toHttp://localhost: 3001/about, when you refresh in About and cut back to the home page, you will not walk the server's data, but the local state data.
About refresh results:
Cut it back like this:
problem
It only solves the problem of loading data on the first screen, not the problem of routing switching on the client side.
If the configuration item asyncData is also found in the client's build, it also needs to be executed
thinking
Join global mixin
stayMain.jsMedium mixed mixin
// Add global mixin // Mix into beforeMount hooks // beforeMount will not be triggered on the server because it renders pages directly to the server and Dom does not hang, so this hook will not be triggered // So beforMount will only be executed on the client side Vue.mixin({ beforeMount() { // The vue instance already exists when this hook executes and has been mounted on the front end.So you can get sotre from this const {asyncData} =this.$options if(asyncData){ //Call as it exists asyncData( { store: this.$store, // this refers to the vue instance route: this.$route } )//Pass parameter here } }, })
OK~That's where the ssr server rendering for first-time vue comes in.
There may be mistyping, but it does not affect the transfer of knowledge, Ha-Ha-Ha-Ha!
If you have any questions, please leave a message at any time. We will discuss and make progress together. Thank you.
Go on with Yes ok!
learning resource
Native: vue ssr https://ssr.vuejs.org/zh/
Framework:nuxt.js https://nuxtjs.org/
github: https://github.com/speak44/ss...