跳至内容

服务器端渲染

已过时

本指南已过时,需要针对 Vue 3 和 vue-apollo 4 进行重写。欢迎贡献!

警告

需要 Vue 2.6+,支持 serverPrefetch

Vue CLI 插件

我为 vue-cli 创建了一个插件,因此您可以将您的 vue-apollo 应用程序在两分钟内转换为一个同构 SSR 应用程序!✨🚀

在您的 vue-cli 3 项目中

bash
vue add @akryum/ssr

更多信息

组件预取

使用以下命令安装 SSR 工具

shell
npm install --save @vue/apollo-ssr

或者

shell
yarn add @vue/apollo-ssr

提示

请遵循 官方 SSR 指南,以了解有关使用 Vue 进行服务器端渲染的更多信息。

默认情况下,使用 vue-server-renderer,您在服务器端渲染的组件中的所有 GraphQL 查询将自动预取。

提示

您可以在 variables 等选项中访问 this,即使在服务器上也是如此!

示例

js
export default {
  apollo: {
    allPosts: {
      query: gql`query AllPosts {
        allPosts {
          id
          imageUrl
          description
        }
      }`,
    }
  }
}

示例 2

js
export default {
  apollo: {
    post: {
      query: gql`query Post($id: ID!) {
        post (id: $id) {
          id
          imageUrl
          description
        }
      }`,
      variables () {
        return {
          id: this.id,
        }
      },
    }
  }
}

跳过预取

您可以使用将 prefetch 选项设置为 false 来跳过对查询的服务器端预取。

不预取查询的示例

js
export default {
  apollo: {
    allPosts: {
      query: gql`query AllPosts {
        allPosts {
          id
          imageUrl
          description
        }
      }`,
      // Don't prefetch
      prefetch: false,
    }
  }
}

如果您想跳过对特定组件的所有查询的预取,请使用 $prefetch 选项

js
export default {
  apollo: {
    // Don't prefetch any query
    $prefetch: false,
    allPosts: {
      query: gql`query AllPosts {
        allPosts {
          id
          imageUrl
          description
        }
      }`,
    }
  }
}

创建 Apollo 客户端

建议在具有 ssr 参数的函数中创建 apollo 客户端,该参数在服务器上为 true,在客户端上为 false

如果 ssr 为 false,我们将尝试使用 cache.restore 通过获取我们在 SSR 期间在服务器上注入到 HTML 页面中的 window.__APOLLO_STATE__ 变量来恢复 Apollo 缓存的状态。

以下是一个示例

js
// apollo.js

import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client/core'

// Create the apollo client
export function createApolloClient (ssr = false) {
  const httpLink = new HttpLink({
    // You should use an absolute URL here
    uri: ENDPOINT + '/graphql',
  })

  const cache = new InMemoryCache()

  // If on the client, recover the injected state
  if (!ssr) {
    if (typeof window !== 'undefined') {
      const state = window.__APOLLO_STATE__
      if (state) {
        // If you have multiple clients, use `state.<client_id>`
        cache.restore(state.defaultClient)
      }
    }
  }

  const apolloClient = new ApolloClient({
    link: httpLink,
    cache,
    ...(ssr ? {
      // Set this on the server to optimize queries when SSR
      ssrMode: true,
    } : {
      // This will temporary disable query force-fetching
      ssrForceFetchDelay: 100,
    }),
  })

  return apolloClient
}

创建应用程序

我们不立即创建根 Vue 实例,而是使用一个接受 context 参数的 createApp 函数。

此函数将在客户端和服务器条目中使用,在上下文中具有不同的 ssr 值。我们在之前编写的 createApolloClient 方法中使用此值。

常见 createApp 方法的示例

js
// app.js

import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import { createStore } from 'vuex'

import VueApollo from '@vue/apollo-option'
import { createApolloClient } from './apollo'

import App from './ui/App.vue'
import routes from './routes'
import storeOptions from './store'

function createMyApp (context) {
  const router = createRouter({
    history: createWebHistory(),
    routes,
  })

  const store = createStore(storeOptions)

  // Vuex state restoration
  if (!context.ssr && window.__INITIAL_STATE__) {
    // We initialize the store state with the data injected from the server
    store.replaceState(window.__INITIAL_STATE__)
  }

  // Apollo
  const apolloClient = createApolloClient(context.ssr)
  const apolloProvider = createApolloProvider({
    defaultClient: apolloClient,
  })

  const app = createApp(App)
  app.use(router)
  app.use(store)
  app.use(apolloProvider)

  return {
    app,
    router,
    store,
    apolloProvider,
  }
}

export default createMyApp

客户端条目

客户端条目非常简单 - 我们只需使用 ssrfalse 来调用 createApp

js
// client-entry.js

import createApp from './app'

createApp({
  ssr: false,
}).mount('#app')

服务器条目

除了将 Apollo 缓存存储起来以将其注入到客户端 HTML 中之外,不需要任何特殊操作。了解有关 使用路由的服务器条目数据预取 的更多信息,请参阅官方 SSR 指南。

以下是一个使用 vue-router 和 Vuex 存储的示例

js
// server-entry.js

import * as ApolloSSR from '@vue/apollo-ssr'
import createApp from './app'

export default () => new Promise((resolve, reject) => {
  const { app, router, store, apolloProvider } = createApp({
    ssr: true,
  })

  // set router's location
  router.push(context.url)

  // wait until router has resolved possible async hooks
  router.onReady(() => {
    // This `rendered` hook is called when the app has finished rendering
    context.rendered = () => {
      // After the app is rendered, our store is now
      // filled with the state from our components.
      // When we attach the state to the context, and the `template` option
      // is used for the renderer, the state will automatically be
      // serialized and injected into the HTML as `window.__INITIAL_STATE__`.
      context.state = store.state

      // ALso inject the apollo cache state
      context.apolloState = ApolloSSR.getStates(apolloProvider.clients)
    }
    resolve(app)
  })
})

使用 ApolloSSR.getStates 方法获取您需要注入到生成的页面中的 JavaScript 代码,以将 apollo 缓存数据传递给客户端。

页面模板 中,使用 renderState 助手

html
{{{ renderState({ contextKey: 'apolloState', windowKey: '__APOLLO_STATE__' }) }}}

以下是一个完整的示例

html
<!DOCTYPE html>
<html>
  <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">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title>{{ title }}</title>
    {{{ renderResourceHints() }}}
    {{{ renderStyles() }}}
  </head>
  <body>
    <!--vue-ssr-outlet-->
    {{{ renderState() }}}
    {{{ renderState({ contextKey: 'apolloState', windowKey: '__APOLLO_STATE__' }) }}}
    {{{ renderScripts() }}}
  </body>
</html>