[关闭]
@qinyun 2018-05-03T17:11:02.000000Z 字数 14217 阅读 4163

用Nuxt.js创建服务端渲染的Vue.js应用程序

未分类


Nuxt.js是一个Vue同构应用程序开发框架。本文将介绍为什么要选择Nuxt、如何创建一个Nuxt项目、Nuxt项目结构、Nuxt的强化组件、使用服务端渲染时的考量、Nuxt在各种环境的部署以及涉及的一些基本概念。希望能够鼓励你尝试Nuxt来进行快速开发,并利用Nuxt提供的强大功能创造出交互丰富的卓越用户体验。

像Vue之类的JavaScript框架(或库)可以在浏览你的网站时提供奇妙的用户体验。大多数框架都提供了一种动态改变页面内容,而无需每次都向服务端发送一个请求。

然而,这种方案有一个问题。刚开始加载你的网站时,浏览器不会接收到完整的显示页面。相反地,浏览器收到一堆用来构建页面的代码片段(HTML、CSS和其它文件)和如何将这些代码片段组装起来的指令(一个JavaScript框架或库)。在浏览器真正有可以显示的东西之前,需要先花费一段时间来将这些信息拼装起来。这有点像收到一堆书和一个扁平包装的书柜。

解决这个问题的方案很巧妙:在服务端放一个能够构建出随时显示的页面的框架(或库)版本,然后将这个完整页面发送给浏览器,附带进一步改变的能力和动态页面内容(框架/库),就像发送一个现有的书柜和一些书,当然,你仍然需要将这些书放到书柜中,但是你已经得到了一些立即可用的东西。

toptal-blog-image-1524038098434-e52e7afe1992c84dec85c44f5ed1c13a.png-118kB

除了这个可能不太巧妙的比喻,Nuxt还有许多其它优势。举个例子,一个很少改变的页面,比如一个关于我们页面,并不需要每次在用户访问它的时候重新创建。因此,服务器可以只创建它一次,然后将它缓存或存储在某个地方以备将来使用。这种速度提升可能看起来很小,但是在一个响应时间以毫秒(或更少)来衡量的环境下,每一点小的提升都很重要。

如果你想了解关于Vue环境下的服务端渲染(Server-Side Rendering,SSR)优势的更多信息,可以查看Vue自己的关于服务端渲染的文章。虽然有许多实现服务端渲染的可选方案,但是Nuxt是最流行的方案,也是被Vue团队所推荐的方案

为什么选Nuxt.js

Next是一个针对流行的React库的服务端渲染实现。在看到了这种设计的优势之后,以它为基础,设计了一个针对Vue的类似实现,即Nuxt。熟悉React+Next组合的人,会在设计和应用程序布局方面发现许多相似之处。然而,Nuxt提供了一些Vue特有的功能来针对Vue创建一个功能强大而灵活的服务端渲染解决方案。

Nuxt在2018年1月份更新到1.0生产环境就绪版本,并且成为活跃的受到广泛支持的社区的一部分。其中最重要的一点是,使用Nuxt构建一个项目和构建其它Vue项目没有太多不同。事实上,它提供了一堆特性,使你能够以更少的时间创建结构良好的代码库。

另外值得一提的是,Nuxt不一定非要用于服务端渲染。它被推动作为一个Vue.js同构应用程序开发库,包含了一个使用相同代码创建静态生成的Vue应用程序的命令(nuxt generate)。因此,如果你对于深入研究服务端渲染顾虑重重,不要惊慌。你仍然可以利用Nuxt的特性创建一个静态站点。

我们可以创建一个简单的项目来深入理解Nuxt。这个项目的最终源码托管在GitHub上,你可以随便查看,或者,你可以查看托管在Netlify上的使用nuxt generate创建的实时版本

创建一个Nuxt项目

首先,让我们使用一个名为vue-cli的Vue项目生成器快速创建一个示例项目:

  1. # install vue-cli globally
  2. npm install -g vue-cli
  3. # create a project using a nuxt template
  4. vue init nuxt-community/starter-template my-nuxt-project

经过几个选项之后,就会在my-nuxt-project目录或者你指定的其它目录创建一个项目。然后我们只需要安装依赖,运行服务器:

  1. cd my-nuxt-project
  2. npm install # Or yarn
  3. npm run dev

这样做之后,确保项目正在运行,然后打开浏览器,访问localhost:3000。这跟创建一个Vue Webpack项目没有太多不同。然而,当我们查看应用程序的实际结构时,并没有太多文件,特别是和类似Vue Webpack模版之类的文件比较起来的话。

toptal-blog-image-1524038149943-65cd6aca270907a12410a09b207714f2.png-26.8kB

package.json也显示我们只有一个依赖——Nuxt它本身。这是因为Nuxt的每个版本都是针对特定版本的Vue、Vue-router和Vuex进行定制并将它们打包在一起的。

在项目的根目录还有一个nuxt.config.js文件。这使得你能够自定义许多Nuxt提供的功能。默认情况下,它会为你设置头部标签、加载条颜色以及ESLint规则。如果你着急想看看你能够进行哪些配置,这里有一个文档链接;我们将在本文介绍到其中的一些配置项。

那么,这些目录有什么特别的呢?

项目结构

如果你浏览一遍创建出来的目录,它们都会有一个附带的Readme文件。这个Readme文件通常以一段介绍这个目录是干什么的简短总结和一个文档链接开头。

使用Nuxt的一个好处是:一个默认的应用程序结构。任何优秀的前端开发者都会类似这样来安排应用程序结构,但是关于应用程序结构存在许多不同的想法,而团队协作时,不可避免地会投入一些时间来讨论或选择应用程序结构。Nuxt给你提供了一个,(通常就可以避免再花费这些时间)。

Nuxt会寻找特定的目录,然后基于它找到的目录来为你构建你的应用程序。让我们逐个看看这些目录。

Pages

这是唯一的必需目录。这个目录中的任何Vue组件都会基于它们的文件名称和目录结构自动添加到vue-router中。这点真是超级方便。通常,我会有一个独立的Pages目录,然后必需手动将其中的每个组件注册到另一个路由文件中。对于比较大型的项目,这个路由文件会变得复杂,可能需要进行拆分来维持可读性。相反地,Nuxt会为你处理所有这些逻辑。

为了演示,我们可以在Pages目录创建一个名为about.vue的Vue组件。让我们添加一个简单的模板:

  1. <template>
  2. <h1>About Page</h1>
  3. </template>

当你保存的时候,Nuxt会为你重新生成对应的路由。可以看到,因为我们将组件命名为about.vue,如果你导航到/about,你应该就会看到那个组件。很简单。

有一个文件名很特别。将一个文件命名为index.vue会为那个目录创建一个根路由。当项目生成的时候,在Pages目录已经有一个index.vue组件,它关联到你站点的主页或着陆页(landing page)。(在这个开发示例中,指的是localhost:3000。)
toptal-blog-image-1524038247087-c67db185d77daded2cdcc1018f20cc3d.png-18.6kB

更深的路由会怎么样呢?Pages目录的子目录可以帮你构建自己的路由结构。因此,如果我们想要一个View Product页面,我们可以将Pages目录构建成如下的结构:

  1. /pages
  2. --| /products
  3. ----| index.vue
  4. ----| view.vue

现在,如果我们导航到/products/view,我们会看到products目录中的view.vue组件。如果导航到products,我们会看到products目录中的index.vue目录。

你可能会问,我们为什么不在Pages目录创建一个products.vue组件,就像我们创建/about页面那样。你可能会认为结果是相同的,但是这两种结构有一点区别。让我们通过添加另一个新页面来演示这个区别:

假如说,我们想要为每个员工创建一个单独的About页面。例如,为我创建一个About页面。这个页面应该位于/about/ben-jones。一开始,我们可能像下面这样构建Pages目录结构:

  1. /pages
  2. --| about.vue
  3. --| /about
  4. ----| ben-jones.vue

当我们访问/about/ben-jones,会得到about.vue组件,和访问/about时一样。这里发生了什么?

有意思的是,Nuxt在这里构建了一个嵌套的路由。这种结构表示,你想要一个永久的/about路由,并且那个路由中的任何东西都应该嵌套在它自己的视图区域。在vue-router中,这会通过在about.vue组件中指定一个<router-view />组件来表示。在Nuxt中,概念相同,但我们用<nuxt />而不是<router-view />。让我们更新about.vue组件来实现嵌套路由:

  1. <template>
  2. <div>
  3. <h1>About Page</h1>
  4. <nuxt />
  5. </div>
  6. </template>

现在,当我们导航到/about,我们得到了之前的about.vue组件,只有一个标题。然而,当我们导航到/about/ben-jones时,我们会看到那个标题和渲染在<nuxt/>占位符所在位置的ben-jones.vue组件。

这并不是我们最初想要的,但是这个想法(存在一个人物列表,每个人物有一个About页面,当点击的时候,将对应人物信息填充到页面的某个区域)是一个有意思的概念,我们目前可以先把它放到一边。如果你确实想要其它选项,那么我们要做的就是重新构建我们的目录结构。我们只需要将about.vue组件移动到/about目录,然后将它重命名为index.vue,因此,得到的目录结构会是:

  1. /pages
  2. --| /about
  3. ----| index.vue
  4. ----| ben-jones.vue

最终,假如说,我们想要使用路由参数来获取某个具体的产品。例如,我想要通过导航到/products/edit/64来编辑某个产品,而64是product_id。我们可以实现如下:

  1. /pages
  2. --| /products
  3. ----| /edit
  4. ------| _product_id.vue

注意_product_id.vue组件开头的下划线,这表示一个路由参数,可以稍后在$route.params对象或者Nuxt的Context中的params对象(稍后再作更多介绍)上访问这个参数。注意,这个参数的键是组件名称去掉开始的下划线,在这个例子中就是product_id,因此使它们在整个项目中保持唯一。因此,在_product_id.vue中,我们会有如下代码:

  1. <template>
  2. <h1>Editing Product {{ $route.params.product_id }}</h1>
  3. </template>

你可以想象更复杂的布局,那些布局使用vue-router配置会非常痛苦。例如,我们可以将上述内容都组合到一个如下路由中:

  1. /pages
  2. --| /categories
  3. ----| /_category_id
  4. ------| products.vue
  5. ------| /products
  6. --------| _product_id.vue

不难推理出/categories/2/products/3会显示什么。我们会得到products.vue组件和一个嵌套的_product_id.vue组件,附带2个路由参数:category_idproduct_id。进行推理时,这比同等的路由配置要简单得多。

当我们谈到这个话题时,我想要在路由配置中做的一件事是设置路由拦截。由于Nuxt为我们构建路由,因此我们可以通过组件本身的beforeRouterEnter设置路由拦截。如果你想要验证路由参数,Nuxt提供了一个名为validate的组件方法。因此,如果你想要在渲染组件之前检查product_id是否是一个数字,你需要在_product_id.vue的script标签中增加如下代码:

  1. export default {
  2. validate ({ params }) {
  3. // Must be a number
  4. return /^\d+$/.test(params.product_id)
  5. }
  6. }

现在,导航到/categories/2/products/someproduct会返回一个404页面,因为someproduct不是一个正确的数字。

这就是Pages目录的内容。了解如何在这个目录中恰当构建你的路由结构是很重要的,因此,一开始花一些时间来学习它是很重要的。如果你正在寻找一份概要,参考路由相关文档通常会有所帮助。

如果你担心路由不受控制,那大可不必。这个默认设置对于各种各样的项目来说都运行得很好,只要它们结构良好。然而,有一些情况下,你可能需要增加一些Nuxt自动为你生成的路由之外的路由,或者需要重构它们。Nuxt提供了一种在配置中自定义路由实例的方法,使你能够新增路由并自定义生成的路由。你还可以编辑路由实例的核心功能,包括Nuxt增加的额外选项。因此,如果你确实遇到了一个极端例子,你还可以灵活地找到合适的解决方案。

Store

和Pages目录有点类似,Nuxt可以基于Store目录结构构建你的Vuex store。如果你不需要store,只需要删除这个目录就可以了。有两种模式的store:经典模式和模块模式。

经典模式需要在Store目录有一个index.js文件。在这个文件中,你需要导出一个返回了一个Vuex实例的函数:

  1. import Vuex from 'vuex'
  2. const createStore = () => {
  3. return new Vuex.Store({
  4. state: ...,
  5. mutations: ...,
  6. actions: ...
  7. })
  8. }
  9. export default createStore

这使你能够创建任何你想要的store,这与在一个普通Vue项目中使用Vuex非常像。

模块模式也需要你在Store目录创建一个index.js文件。然而,这个文件只需要为你的Vuex store导出根state/mutations/actions。下面的例子指定了一个空的根state:

  1. export const state = () => ({})

然后,store目录中的每个文件都会以它自己的命名空间或模块增加到store中。例如,让我们创建某个store来存储当前产品。如果我们在store目录创建一个名为product.js的文件,那么可以在$store.product访问store下对应命名空间的部分。下面是一个简单的例子:

  1. export const state = () => ({
  2. _id: 0,
  3. title: 'Unknown',
  4. price: 0
  5. })
  6. export const actions = {
  7. load ({ commit }) {
  8. setTimeout(
  9. commit,
  10. 1000,
  11. 'update',
  12. { _id: 1, title: 'Product', price: 99.99 }
  13. )
  14. }
  15. }
  16. export const mutations = {
  17. update (state, product) {
  18. Object.assign(state, product)
  19. }
  20. }

在load action中的setTimeout模拟了某种类型的API调用,会在响应中更改store;在这个例子中,它需要1秒钟(才会响应)。现在,让我们在products/view页面中使用它:

  1. <template>
  2. <div>
  3. <h1>View Product {{ product._id }}</h1>
  4. <p>{{ product.title }}</p>
  5. <p>Price: {{ product.price }}</p>
  6. </div>
  7. </template>
  8. <script>
  9. import { mapState } from 'vuex'
  10. export default {
  11. created () {
  12. this.$store.dispatch('product/load')
  13. },
  14. computed: {
  15. ...mapState(['product'])
  16. }
  17. }
  18. </script>

需要注意:这里,我们是在组件创建时,调用了我们假的API。你可以看到,我们正在调度的product/load action是在Product命名空间下。这样,我们正在处理store的哪个部分就显得非常清楚。然后,通过将state映射到一个本地的computed属性,我们可以在模版中轻松使用它。

有一个问题:在API运行的时候,我们可以持续看到原始状态1秒钟。稍后,我们会使用Nuxt提供的一种解决方案来修复这个问题(称为fetch)。

再次强调,我们从来不需要运行npm install vuex,因为它已经包含在Nuxt包中了。当你在store目录增加一个index.js文件时,所有这些方法都会向你自动打开。

其中主要的2个目录已经解释了;剩下的目录就简单多了。

Components

Components目录用来包含你的可复用组件,例如导航条、图片夹、分页、数据表格等。由于Pages目录中的组件会被转换为路由,因此你需要在其它地方存储这些组件类。可以通过引用这些组件而在页面或其它组件中访问它们:

  1. import ComponentName from ~/components/ComponentName.vue

Assets

这个目录包含未编译的资源,与Webpack如何加载和处理文件有更多关系,而与Nuxt如何工作没有太多关系。如果你对此感兴趣,可以阅读Readme中的指南

Static

这个目录包含一些映射到你的站点的根目录的静态文件。例如,将一个名为logo.png的图片放到这个目录,就可以在/logo.png访问它。这很适合一些诸如robots.txt、favicon.ico和其它你需要可被访问的元文件。

Layouts

通常,在一个Vue项目中,你有某种根组件,通常名为App.vue。在这里,你可以设置你的(通常是静态的)应用程序的布局。这个布局可能会包含一个导航栏、页脚和一个用于vue-router的内容区。default布局就是这样的,在layouts目录中提供了这种默认布局。最初,它只有一个div和一个<nuxt />组件(等同于<router-view />),但它可以任你调整。例如,我向示例项目中增加了一个简单的导航栏,用于导航各种演示页面。
toptal-blog-image-1524038351638-07ba9508d7561c57c6253def49848067.png-19.2kB
你可能会想让你的App的特定区域有一种不同的布局。也许,你有某种看起来不太一样的内容管理系统(CMS)或者管理看板。为了解决这个问题,可以在Layouts目录创建一个新的布局。举个例子,让我们创建一个admin-layout.vue布局,只有一个额外的header标签而没有导航条:

  1. <template>
  2. <div>
  3. <h1>Admin Layout</h1>
  4. <nuxt />
  5. </div>
  6. </template>

然后,我们可以在Pages目录创建一个admin.vue页面,然后使用Nuxt提供的一个名为layout的属性来指定我们想要为那个组件使用的布局名称(一个字符串)。

  1. <template>
  2. <h1>Admin Page</h1>
  3. </template>
  4. <script>
  5. export default {
  6. layout: 'admin-layout'
  7. }
  8. </script>

这就是所有代码了。Page组件将使用默认布局除非指定了布局,但是当你导航到/admin,它现在使用admin-layout.vue布局。当然,如果你想的话,这个布局可以在几个管理屏幕之间共享。需要牢记的是,布局必须包含一个<nuxt />元素

关于布局,还有最后一点需要注意。你可能已经在实验时注意到了,如果你输入一个不正确的URL,会显示一个错误页面。事实上,这个错误页面是另外一种布局。Nuxt有它自己的错误页面布局(源代码链接),但是如果你想要编辑它,只需要创建一个error.vue布局,然后Nuxt就会使用这个布局代替默认的布局。这里需要提醒的是,错误页面布局一定不要有一个<nuxt />元素。你还可以访问组件上的一个error对象,它包括一些要显示的基本信息。(这会打印在运行Nuxt的终端中,如果你想要检查这点的话。)

Middleware

中间件(Middleware)是一些可以在渲染一个页面或布局之前被执行的函数。有各种各样的原因你想要这样做。路由拦截是一种流行用途,其中你可以通过检查Vuex store来校验登录是否合法,或者校验一些参数(而不是在组件本身上使用validate方法)。我最近从事的一个项目使用中间件来基于路由和参数生成动态面包屑导航(breadcrumbs)。

这些函数可以是异步的;需要小心,因为直到中间件处理完毕之前不会向用户显示任何东西。这些函数还可以访问到Nuxt的Context,我稍后会解释这一点。

Plugins

这个目录使你能够在应用程序被创建之前注册Vue插件。这使得插件可以在你的应用程序上的Vue实例中共享,并可以在任何一个组件中使用。

大部分主流插件都有Nuxt版本,通过遵循它们的文档,可以轻松将它们注册到Vue实例。然而,你也可以开发一个插件或者对一个现有插件进行适配。我从Nuxt文档中借用的一个例子展示了如何对vue-notifications进行适配。首先,我们需要安装这个包:

  1. npm install vue-notifications --save

然后,在plugins目录创建一个名为vue-notifications的文件,包含如下内容:

  1. import Vue from 'vue'
  2. import VueNotifications from 'vue-notifications'
  3. Vue.use(VueNotifications)

这与你向普通的Vue环境注册一个插件非常相似。然后在你项目的根目录编辑nuxt.config.js文件,并将下面的内容添加到module.exports对象中:

  1. plugins: ['~/plugins/vue-notifications']

这就行了。现在,你可以在你的应用程序中使用vue-notifications。在示例项目的/plugin有一个例子。

这样就逐个介绍完了Nuxt目录结构。这可能看起来有很多东西要学,但是如果你正在开发一个Vue应用程序,你其实已经在设置相同类型的逻辑。Nuxt只是帮助简化了设置,帮你将精力集中在应用程序构建上。

其实,Nuxt做的不仅仅是辅助开发。它通过提供的额外功能,强化了你的组件。

Nuxt的强化组件

当我刚开始研究Nuxt的时候,我一直在看Page组件是如何被强化的。强化组件虽然听起来很棒,但是它究竟意味着什么以及它能带来什么好处并不能立即显现出来。

强化组件意味着,Nuxt给所有的Page组件绑定了额外的方法,Nuxt可以使用这些方法来提供额外的功能。事实上,我们先前使用validate方法来检查参数和重定向非法用户时,已经看过其中一个强化组件了。

在一个Nuxt项目中被使用的2个主要方法是asyncDatafetch方法。它们在概念上非常相似,在组件生成之前异步运行,而且它们可以用来获取组件和sotre的数据。它们还能够使页面在发送到客户端之前在服务端完全渲染,即使我们必须等待一些数据库或API调用。

asyncDatafetch之间有什么不同?
- asyncData是用来获取Page组件的数据。当你返回一个对象,它会在渲染之前,与输出数据进行合并。
- fetch是用来获取Vuex Store。如果你返回一个promise,Nuxt会在渲染之前一直等待promise处理完毕。

那么,让我们好好利用这些功能。还记得在/products/view页面,我们有一个问题,即在我们调用假API时,store的最初状态被会短暂显示?修复这个问题的一个方法是,在组件或Store上存储一个布尔值,例如loading = true,然后在调用API时显示一个加载中组件。API调用完成之后,我们设置loading = false并显示数据。

作为替代,我们可以在渲染之前使用fetch获取Store。在一个名为/products/view-async的新页面,我们将created方法改为fetch;这样应该可行,对吧?

  1. export default {
  2. fetch () {
  3. // Unfortunately the below line throws an error
  4. // because 'this.$store' is undefined...
  5. this.$store.dispatch('product/load')
  6. },
  7. computed: {...}
  8. }

这里有个问题:这些“强化”方法在组件被创建之前运行,因此不会指向对应的组件,而且不能访问组件上的任何东西。那么,我们这里如何访问Store?

The Context API

当然是有方案的。在所有Nuxt方法中,会被提供一个包含一个Context对象的参数(通常是第一个参数),这个对象非常有用。这个对象中,就是你就会跨应用程序引用到的任何东西。这意味着,我们不需要等待Vue预先在对应组件上创建那些引用。

我强烈推荐查看Context文档来看看有什么可用的内容。比较方便的有app,你可以通过它访问你的所有插件;redirect,可以用来改变路由;error用来显示错误页面;以及一些一目了然的属性,例如routequerystore等。

因此,为了访问Store,我们可以解构Context并从中提取Store。我们还需要确保返回了一个promise,这样Nuxt可以在渲染组件之前等待这个promise解决,因此我们还需要对我们的Store action做一些小的调整。

  1. // Component
  2. export default {
  3. fetch ({ store }) {
  4. return store.dispatch('product/load')
  5. },
  6. computed: {...}
  7. }
  8. // Store Action
  9. load ({ commit }) {
  10. return new Promise(resolve => {
  11. setTimeout(() => {
  12. commit('update', { _id: 1, title: 'Product', price: 99.99 })
  13. resolve()
  14. }, 1000)
  15. })
  16. }

你可以根据自己的编码风格,使用async/await或者其它方法,但是概念是相同的——我们让Nuxt在尝试渲染组件之前确保API已经调用结束并且Store已经用返回结果更新了。如果你导航到/products/view-async,不会再闪现处于原始状态的产品内容。

你可以想象,即使没有服务端渲染,这在任何Vue应用程序中会多么有用。Context还可以在所有中间件以及其它Nuxt方法,例如nuxtServerInit中访问。nuxtServerInit是一个特殊的store action,在Store初始化之前运行(下个章节中有一个关于它的例子)。

使用服务端渲染时的考量

我确信,许多开始使用Nuxt之类技术的人(包括我自己),在将它当作任何其它Vue项目的时候,最后都会碰壁——我们通常认为可以起效的事情在Nuxt中看起来是不可能的。随着更多的这些警告被记录,这会变得更容易克服,但是在开始调试时主要需要考虑的是,客户端和服务端是两个独立的实体。

当你刚开始访问一个页面的时候,会向Nuxt发送一个请求,服务端会尽可能多地构建那个页面和应用程序的剩余部分,然后将它发送给你。然后,客户端负责继续导航并按需加载对应的部分。

我们想要服务端在开始的时候尽可能多地做事情,但是有时候,服务端并没有它所需要的信息,从而导致相应的工作要在客户端完成。更糟糕的是,当客户端展示的最终内容与服务端预期的内容不同时,会让客户端重头开始重新构建。这是一个很大的迹象表明应用程序的逻辑在某些地方出了问题。谢天谢地,(在开发环境),如果应用程序的逻辑发生了错误,在你的浏览器控制台会生成一个错误。

让我们以如何解决会话管理这个常见问题为例。想象你有一个Vue应用程序,你可以登录一个账户,然后使用一个token(例如JWT)存储你的会话,你可能决定将这个token保持在localStorage。当你一开始访问站点的时候,你想要通过一个API来验证那个token,如果token合法,那个API会返回一些基本的用户信息并将这些信息放到Store中。

读完Nuxt的文档之后,你可以看到,有一个方便的方法nuxtServerInit可以使你在刚开始加载的时候获取Store数据。这听起来很完美!因此,你在Store中创建了你的用户模块,并在Store目录的index.js文件中增加适当的action:

  1. export const actions = {
  2. nuxtServerInit ({ dispatch }) {
  3. // localStorage should work, right?
  4. const token = localStorage.getItem('token')
  5. if (token) return dispatch('user/load', token)
  6. }
  7. }

当你刷新页面,你会得到一个错误——localStorage is not defined(localStorage未被定义)。想一想这个错误是在哪里发生的,一切就清楚了。这个方法运行在服务端,而服务端不清楚客户端上的localStorage中存储了什么;事实上,服务端甚至不知道“localStorage“是什么!因此,上述方法是行不通的。
toptal-blog-image-1524038432951-2dcf00c54d63f788e4137960bb09fccb.png-39.1kB

那么,解决方案是什么呢?事实上,还是有一些解决方案的。你可以让客户端来初始化Store,但会丧失服务端渲染的好处,因为客户端最终做了所有事情。你可以在服务端建立会话,然后用它来验证用户,但那又是另一个层次的事情了。最类似于localStorage的替代方法是使用cookies。

Nuxt可以访问cookies,因为它们会随着请求一起从客户端发送到服务端。和其它Nuxt方法一样,nuxtServerInit可以访问Context,这次是作为第二个参数,因为第一个参数是保留给store的。在Context上,我们可以访问req对象,它存储了所有的请求头和客户端请求的其它信息。(如果你用过Node.js,就会对这些感到特别熟悉。)

因此,在一个cookie中存储token(本例中,称为“token”)之后,让我们在服务端访问它。

  1. import Cookie from 'cookie'
  2. export const actions = {
  3. nuxtServerInit ({ dispatch }, { req }) {
  4. const cookies = Cookie.parse(req.headers.cookie || '')
  5. const token = cookies['token'] || ''
  6. if (token) return dispatch('user/load', token)
  7. }
  8. }

这是一个简单的解决方案,但可能不是能够让人立即明白。学着去思考,特定的活动发生在什么地方(客户端、服务端或者同时这两端),以及它们访问的哪些内容虽然会需要一些时间但其好处是值得的。

部署

部署Nuxt非常简单。使用同样的代码,你可以创建一个服务端渲染应用程序、单页应用程序或者静态页面。

服务端渲染应用程序(SSR App)

这可能就是你使用Nuxt的目的。部署的基本概念是,在你选择的任何平台运行build进程并设置一些配置。我将使用文档中的Heroku例子:

首先,在package.json中设置好Heroku的脚本:

  1. "scripts": {
  2. "dev": "nuxt",
  3. "build": "nuxt build",
  4. "start": "nuxt start",
  5. "heroku-postbuild": "npm run build"
  6. }

然后使用heroku-cli安装好Heroku环境(安装指令如下

  1. # set Heroku variables
  2. heroku config:set NPM_CONFIG_PRODUCTION=false
  3. heroku config:set HOST=0.0.0.0
  4. heroku config:set NODE_ENV=production
  5. # deploy
  6. git push heroku master

这就可以了。现在,你的服务端渲染Vue应用程序已经上线了,全世界都看得到。其它平台的安装步骤不同,但是过程类似。目前其官方部署方法列举如下:

单页应用程序(SPA)

如果你想要利用Nuxt提供的其它特性,但是避免服务端渲染页面,那么你可以将它作为一个SPA部署。

首先,最好关闭服务端渲染来测试你的应用程序,因为默认地,npm run dev运行时会开启服务端渲染。可以编辑nuxt.config.js文件并增加如下配置项来改变这点:

  1. mode: 'spa',

现在,当你运行npm run dev,服务端渲染会被关闭,而应用程序会作为一个单页应用程序运行来供你测试。这个设置也会确保未来的构建都不会包含服务端渲染。

如果看起来一切正常,那么其部署过程和一个服务端渲染应用程序其实是相同的。只要记住,你需要先设置mode: 'spa'来让构建进程知道你想要一个单页应用程序。

静态页面

如果你一点儿也不想和服务器打交道,而只想要生成一些页面,用于静态托管服务,例如Surge或Netlify,那么你也可以用Nuxt。只需要牢记,没有服务器,你就不能在Context中访问reqres,因此,如果你的代码依赖这些,最好确保对它进行适配。例如,在生成示例项目时,nuxtServerInit函数因为试图从请求头的cookies中获取token而抛出了一个错误。在这个项目中,这没什么影响,因为那些数据在任何地方都不会被用到,但是在一个真实的应用程序中,就会需要有一种备选方法来获取那些数据。

一旦就绪,部署就很简单了。你可能首先需要改变的是,增加一个配置项,从而使nuxt generate命令也会创建一个反馈文件。这个文件会提示托管服务让Nuxt处理路由而不是托管服务处理路由,抛出一个404错误。为了实现这点,要向nuxt.config.js中增加如下一行代码:

  1. generate: { fallback: true },

这里有一个使用Netlify的例子,目前不在Nuxt文档中。只要牢记,如果你是第一次使用netlify-cli,你会被提示进行身份验证:

  1. # install netlify-cli globally
  2. npm install netlify-cli -g
  3. # generate the application (outputs to dist/ folder)
  4. npm run generate
  5. # deploy
  6. netlify deploy dist

就是这么简单!正如本文开头说的,这里有一个这个项目的版本。下述服务也有各自的官方部署文档:

更多了解

Nuxt正迅速更新,这只是挑选出来的一小部分它提供的特性。我希望本文能够鼓励你去尝试它,并看看它是否能够帮助提升你的Vue应用程序的能力,使你能够更快地开发并利用它的强大功能。

如果你想要获取更多信息,那么没有比Nuxt的官方链接更透彻的了:

查看英文原文:Creating Server-side Rendered Vue.js Apps Using Nuxt.js

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注