diff --git a/src/guide/ssr/routing.md b/src/guide/ssr/routing.md index 05fe4575f9..e52fd982ec 100644 --- a/src/guide/ssr/routing.md +++ b/src/guide/ssr/routing.md @@ -8,34 +8,50 @@ It is recommended to use the official [vue-router](https://github.com/vuejs/vue- ```js // router.js -import { createRouter, createMemoryHistory, createWebHistory } from 'vue-router' +import { createRouter } from 'vue-router' import MyUser from './components/MyUser.vue' -const isServer = typeof window === 'undefined' - -const createHistory = isServer ? createMemoryHistory : createWebHistory - const routes = [{ path: '/user', component: MyUser }] -export default function() { +export default function (history) { return createRouter({ - history: createHistory(), + history, routes }) } ``` -And update our `app.js`, client and server entries: +And update our client and server entries: + +```js +// entry-client.js +import { createApp } from 'vue' +import { createWebHistory } from 'vue-router' +import createRouter from './router.js' +import App from './App.vue' + +// ... + +const app = createApp(App) + +const router = createRouter(createWebHistory()) + +app.use(router) + +// ... +``` ```js -// app.js +// entry-server.js import { createSSRApp } from 'vue' +// server router uses a different history from the client one +import { createMemoryHistory } from 'vue-router' +import createRouter from './router.js' import App from './App.vue' -import createRouter from './router' -export default function(args) { - const app = createSSRApp(App) - const router = createRouter() +export default function () { + const app = createSSRApp(Vue) + const router = createRouter(createMemoryHistory()) app.use(router) @@ -46,20 +62,6 @@ export default function(args) { } ``` -```js -// entry-client.js -const { app, router } = createApp({ - /*...*/ -}) -``` - -```js -// entry-server.js -const { app, router } = createApp({ - /*...*/ -}) -``` - ## Code-Splitting Code-splitting, or lazy-loading part of your app, helps reduce the size of assets that need to be downloaded by the browser for the initial render, and can greatly improve TTI (time-to-interactive) for apps with large bundles. The key is "loading just what is needed" for the initial screen. @@ -77,15 +79,20 @@ const routes = [ ] ``` -On both client and server we need to wait for router to resolve async route components ahead of time in order to properly invoke in-component hooks. For this we will be using [router.isReady](https://next.router.vuejs.org/api/#isready) method Let's update our client entry: +On both client and server we need to wait for the router to resolve async route components ahead of time in order to properly invoke in-component hooks. For this we will be using [router.isReady](https://next.router.vuejs.org/api/#isready) method Let's update our client entry: ```js // entry-client.js -import createApp from './app' +import { createApp } from 'vue' +import { createWebHistory } from 'vue-router' +import createRouter from './router.js' +import App from './App.vue' -const { app, router } = createApp({ - /* ... */ -}) +const app = createApp(App) + +const router = createRouter(createWebHistory()) + +app.use(router) router.isReady().then(() => { app.mount('#app') diff --git a/src/guide/ssr/server.md b/src/guide/ssr/server.md index 0a8b304c04..8f38e7d4ce 100644 --- a/src/guide/ssr/server.md +++ b/src/guide/ssr/server.md @@ -2,13 +2,14 @@ The [code structure](./structure.html) and [webpack configuration](./build-config.html) we've described also require some changes to our Express server code. -- we need to create an application with a built `app.js` from the resulting bundle. A path to it can be found using the webpack manifest: +- we need to create an application with a built `entry-server.js` from the resulting bundle. A path to it can be found using the webpack manifest: ```js // server.js const path = require('path') const manifest = require('./dist/server/ssr-manifest.json') + // the 'app.js' name is taken from the name of the entrypoint with an added `.js` postfix const appPath = path.join(__dirname, './dist', 'server', manifest['app.js']) const createApp = require(appPath).default ``` @@ -78,7 +79,7 @@ server.use( ) server.get('*', async (req, res) => { - const { app } = await createApp() + const { app } = createApp() const appContent = await renderToString(app) diff --git a/src/guide/ssr/structure.md b/src/guide/ssr/structure.md index 1fe86d676a..7c1095be20 100644 --- a/src/guide/ssr/structure.md +++ b/src/guide/ssr/structure.md @@ -4,11 +4,15 @@ When writing client-only code, we can assume that our code will be evaluated in a fresh context every time. However, a Node.js server is a long-running process. When our code is first imported by the process, it will be evaluated once and then stay in memory. This means that if you create a singleton object, it will be shared between every incoming request, with the risk of cross-request state pollution. -Therefore, we need to **create a new root Vue instance for each request.** In order to do that, we need to write a factory function that can be repeatedly executed to create fresh app instances for each request: +Therefore, we need to **create a new root Vue instance for each request.** In order to do that, we need to write a factory function that can be repeatedly executed to create fresh app instances for each request, so our server code now becomes: ```js -// app.js +// server.js const { createSSRApp } = require('vue') +const { renderToString } = require('@vue/server-renderer') +const express = require('express') + +const server = express() function createApp() { return createSSRApp({ @@ -20,15 +24,6 @@ function createApp() { template: `
Current user is: {{ user }}
` }) } -``` - -And our server code now becomes: - -```js -// server.js -const { renderToString } = require('@vue/server-renderer') -const server = require('express')() -const { createApp } = require('src/app.js') server.get('*', async (req, res) => { const app = createApp() @@ -49,7 +44,7 @@ server.get('*', async (req, res) => { server.listen(8080) ``` -The same rule applies to other instances as well (such as the router or store). Instead of exporting the router or store directly from a module and importing it across your app, you should create a fresh instance in `createApp` and inject it from the root Vue instance. +The same rule applies to other instances as well (such as the router or store). Instead of exporting the router or store directly from a module and importing it across your app, you should create a fresh instance in `createApp` and inject it from the root Vue instance each time a new request is made. ## Introducing a Build Step @@ -76,42 +71,43 @@ src ├── components │ ├── MyUser.vue │ └── MyTable.vue -├── App.vue -├── app.js # universal entry +├── App.vue # the root of your application ├── entry-client.js # runs in browser only └── entry-server.js # runs on server only ``` -### `app.js` +### `App.vue` -`app.js` is the universal entry to our app. In a client-only app, we would create the Vue application instance right in this file and mount directly to DOM. However, for SSR that responsibility is moved into the client-only entry file. `app.js` instead creates an application instance and exports it: +You may have noticed we now have a file called `App.vue` in the root of our `src` folder. That's where the root component of your application will be stored. We can now safely move the application code from `server.js` to the `App.vue` file: -```js -import { createSSRApp } from 'vue' -import App from './App.vue' - -// export a factory function for creating a root component -export default function(args) { - const app = createSSRApp(App) +```vue + - return { - app + ``` ### `entry-client.js` -The client entry creates the application using the root component factory and mounts it to the DOM: +The client entry creates the application using the `App.vue` component and mounts it to the DOM: ```js -import createApp from './app' +import { createSSRApp } from 'vue' +import App from './App.vue' // client-specific bootstrapping logic... -const { app } = createApp({ - // here we can pass additional arguments to app factory -}) +const app = createSSRApp(App) // this assumes App.vue template root element has `id="app"` app.mount('#app') @@ -122,12 +118,11 @@ app.mount('#app') The server entry uses a default export which is a function that can be called repeatedly for each render. At this moment, it doesn't do much other than returning the app instance - but later we will perform server-side route matching and data pre-fetching logic here. ```js -import createApp from './app' +import { createSSRApp } from 'vue' +import App from './App.vue' -export default function() { - const { app } = createApp({ - /*...*/ - }) +export default function () { + const app = createSSRApp(Vue) return { app