Skip to content

SSR Guide: Remove app.js #1297

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Oct 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 39 additions & 32 deletions src/guide/ssr/routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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.
Expand All @@ -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')
Expand Down
5 changes: 3 additions & 2 deletions src/guide/ssr/server.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
Expand Down Expand Up @@ -78,7 +79,7 @@ server.use(
)

server.get('*', async (req, res) => {
const { app } = await createApp()
const { app } = createApp()

const appContent = await renderToString(app)

Expand Down
65 changes: 30 additions & 35 deletions src/guide/ssr/structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -20,15 +24,6 @@ function createApp() {
template: `<div>Current user is: {{ user }}</div>`
})
}
```

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()
Expand All @@ -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

Expand All @@ -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
<template>
<div>Current user is: {{ user }}</div>
</template>

return {
app
<script>
export default {
name: 'App',
data() {
return {
user: 'John Doe'
}
}
}
</script>
```

### `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')
Expand All @@ -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
Expand Down