如何使用 Nuxt.js 构建 Jamstack 多语言博客
Jamstack(JavaScript、API 和标记堆栈)是一种围绕 Web 项目开发新方式的术语,它允许您无需每次构建网站时都自行托管后端,而是在构建时渲染一组静态页面,并将其部署到内容分发网络 (CDN)。这意味着更高的安全性、更高的可扩展性和更佳的网站性能。
在本教程中,您将学习如何使用Nuxt.js构建 Jamstack 多语言博客。Nuxt.js 是一个强大的 Vue 框架,支持 SPA、SSR 和静态渲染,并结合Strapi Headless CMS来存储和公开数据,从而生成静态博客。要本地设置 Strapi,您可以按照本指南操作,否则,您可以使用我们服务器https://strapi.lotrek.net/上运行的只读实例。
👉🏻 您可以在此存储库中找到本教程的完整代码。
后端结构
使用 Strapi,我构建了一个简单结构来支持翻译,其中包含一个Post
表,其中包含与一个或多个TransPost
包含翻译的元素链接的元素
____________ ____________
| POST | | TRANS_POST |
============ ============
| published | | language |
| created_at | <--(1)-------(N)-->> | title |
| | | content |
| | | slug |
============ ============
您可以使用GraphQL 游乐场进行体验并探索后端。请记住,本教程的重点是Nuxt.js
,您可以使用任何您想要的后端来生成最终的静态网站。后端代码库可在此处获取
设置 Nuxt.js 项目
全局安装 Nuxt.js 并创建一个名为multilangblog
npx create-nuxt-app multilangblog
记得选择axios
选项(稍后您将需要它)并添加 UI 框架,例如Buefy。
创建一个客户端来获取帖子
安装apollo-fetch
客户端以从 Strapi 服务器获取帖子(我使用这个旧包来使客户端部分尽可能简单,请查看@nuxtjs/apollo以获得更结构化和更新的插件)
yarn add apollo-fetch
并在文件夹下创建index.js
文件services
来包装所有查询。此客户端应实现以下三个方法:
getAllPostsHead
:获取特定语言的所有帖子,显示slug
和title
。getAllPosts
:获取特定语言的所有帖子,显示slug
、title
和content
其他语言的帖子 slug 以获取备用 url。getSinglePost
:获取具有特定 slug 和语言的单个帖子,显示所有属性和其他语言的帖子。
import { createApolloFetch } from 'apollo-fetch'
export default class BlogClient {
constructor () {
this.apolloFetch = createApolloFetch({ uri: `${process.env.NUXT_ENV_BACKEND_URL}/graphql` })
}
getAllPostsHead (lang) {
const allPostsQuery = `
query AllPosts($lang: String!) {
transPosts(where: {lang: $lang}) {
slug
title
}
}
`
return this.apolloFetch({
query: allPostsQuery,
variables: {
lang
}
})
}
getAllPosts (lang) {
const allPostsQuery = `
query AllPosts($lang: String!) {
transPosts(where: {lang: $lang}) {
slug
title
content
post {
published
transPosts(where: {lang_ne: $lang}) {
slug
lang
}
}
}
}
`
return this.apolloFetch({
query: allPostsQuery,
variables: {
lang
}
})
}
getSinglePost (slug, lang) {
const simplePostQuery = `
query Post($slug: String!, $lang: String!) {
transPosts(where: {slug : $slug, lang: $lang}) {
slug
title
content
post {
published
transPosts(where: {lang_ne: $lang}) {
slug
lang
}
}
}
}
`
return this.apolloFetch({
query: simplePostQuery,
variables: {
slug,
lang
}
})
}
}
为了使BlogClient
您可以随时访问上下文(例如在asyncData
函数中)创建plugins/ctx-inject.js
文件
import BlogClient from '~/services'
export default ({ app }, inject) => {
app.$blogClient = new BlogClient()
}
并将其添加plugins
到nuxt.config.js
export default {
// ...
plugins: ['~/plugins/ctx-inject.js']
}
创建主视图
这个博客的结构非常简单,在主页 ( /
) 中会有一个文章列表,其中包含一个文章阅读链接 ( /blog/<postslug>
)。现在您可以BlogClient
从上下文中访问实例,开始重写HomePage
组件 ( ),以便在名为asyncDatapages/index.vue
的特殊方法中获取博客文章,并为每篇文章渲染标题和链接。接收上下文作为第一个参数,您的实例可以在asyncData
BlogClient
context.app.$blogClient
<template>
<section class="section">
<div class="is-mobile">
<div v-for="post in posts" :key="post.slug">
<h2>{{ post.title }}</h2>
<nuxt-link :to="{name: 'blog-slug', params:{slug: post.slug}}">Read more...</nuxt-link>
</div>
</div>
</section>
</template>
<script>
export default {
name: 'HomePage',
async asyncData ({ app }) {
const postsData = await app.$blogClient.getAllPostsHead('en')
return { posts: postsData.data.transPosts }
},
data () {
return {
posts: []
}
}
}
</script>
添加/blog/<postslug>
创建组件的路由BlogPost
(pages/blog/_slug.vue
)。安装Vue Markdown 组件以正确呈现文章(yarn add vue-markdown
)
<template>
<section class="section">
<div class="is-mobile">
<h2>{{ post.title }}</h2>
<vue-markdown>{{ post.content }}</vue-markdown>
</div>
</section>
</template>
<script>
export default {
name: 'BlogPost',
components: {
'vue-markdown': VueMarkdown
},
async asyncData ({ app, route }) {
const postsData = await app.$blogClient.getSinglePost(route.params.slug, 'en')
return { post: postsData.data.transPosts[0] }
},
data () {
return {
post: null
}
}
}
</script>
添加 i18n
要设置 i18n,请安装Nuxt i18n 模块
yarn add nuxt-i18n
在文件module
部分启用它nuxt.config.js
{
modules: ['nuxt-i18n']
}
并设置 i18n
const LOCALES = [
{
code: 'en',
iso: 'en-US'
},
{
code: 'es',
iso: 'es-ES'
},
{
code: 'it',
iso: 'it-IT'
}
]
const DEFAULT_LOCALE = 'en'
export default {
// ...
i18n: {
locales: LOCALES,
defaultLocale: DEFAULT_LOCALE,
encodePaths: false,
vueI18n: {
fallbackLocale: DEFAULT_LOCALE,
messages: {
en: {
readmore: 'Read more'
},
es: {
readmore: 'Lee mas'
},
it: {
readmore: 'Leggi di più'
}
}
}
}
// ...
}
现在您可以修改HomePage
组件:nuxt-link
您应该使用并渲染localePath
翻译后的标签readmore
$t
<nuxt-link :to="localePath({name: 'blog-slug', params:{slug: post.slug}})">{{ $t('readmore') }}</nuxt-link>
您可以使用属性asyncData
获取帖子列表以获取当前语言。store.$i18n
context
// ....
async asyncData ({ app, store }) {
const postsData = await app.$blogClient.getAllPostsHead(
store.$i18n.locale
)
return { posts: postsData.data.transPosts }
},
// ....
BlogPost
在组件中执行相同操作route.params.slug
以获取 slug 参数
// ....
async asyncData ({ app, route, store }) {
const postsData = await app.$blogClient.getSinglePost(
route.params.slug, store.$i18n.locale
)
return { post: postsData.data.transPosts[0] }
},
// ....
现在是时候创建一个组件来切换当前语言了,LanguageSwitcher
(components/LanguageSwitcher.vue
)
<template>
<b-navbar-dropdown :label="$i18n.locale">
<nuxt-link v-for="locale in availableLocales" :key="locale.code" class="navbar-item" :to="switchLocalePath(locale.code)">
{{ locale.code }}
</nuxt-link>
</b-navbar-dropdown>
</template>
<script>
export default {
computed: {
availableLocales () {
return this.$i18n.locales.filter(locale => locale.code !== this.$i18n.locale)
}
}
}
</script>
并将其添加到layouts/default.vue
导航栏中。此组件调用switchLocalePath
以获取指向其他语言当前页面的链接。要使语言切换器与动态路由配合使用,您需要使用store.dispatchslug
在BlogPost
组件中设置参数
//...
async asyncData ({ app, route, store }) {
const postsData = await app.$blogClient.getSinglePost(
route.params.slug, store.$i18n.locale
)
await store.dispatch(
'i18n/setRouteParams',
Object.fromEntries(postsData.data.transPosts[0].post.transPosts.map(
el => [el.lang, { slug: el.slug }])
)
)
return { post: postsData.data.transPosts[0] }
},
//...
NUXT_ENV_BACKEND_URL
记得使用BlogClient
.env 或直接( )设置环境变量export NUXT_ENV_BACKEND_URL=https://strapi.lotrek.net
并启动开发服务器
yarn dev
完全静态生成
👉🏻 请注意,本文是我使用 Nuxt.js 2.12.0 编写的,之后我将核心升级到 2.13.0 以使用完整静态生成,请务必运行最新版本。更多信息,请阅读Nuxt.js 官方博客中的“Going Full Static”并关注代码库中的更新。
要使用 Nuxt.js 生成此博客的完整静态版本,请添加target: 'static'
并nuxt.config.js
运行
nuxt build && nuxt export
(您可以将其包装nuxt export
在脚本部分中package.json
)
dist
最终输出是文件夹内生成的路线列表
ℹ Generating output directory: dist/
ℹ Full static mode activated
ℹ Generating pages
✔ Generated /it/
✔ Generated /es/
✔ Generated /
ℹ Ready to run nuxt serve or deploy dist/ directory
✨ Done in 43.49s.
👉🏻 从 2.13.0 版本开始,Nuxt.js 使用 来crawler
检测每个相对链接并生成它。您可以禁用爬虫设置generate.crawler: false
,但仍然可以自行添加动态路由,以提高性能(如本例所示),或者添加爬虫无法检测到的额外路由。
要手动添加动态路线,您必须在设置routes
下实现功能并返回包含您要生成的和包含帖子的对象列表。generate
nuxt.config.js
route
payload
import BlogClient from './services'
// ...
export default {
// ...
crawler: false,
generate: {
routes: async () => {
const client = new BlogClient()
let routes = []
let postsData = []
for (const locale of LOCALES) {
postsData = await client.getAllPosts(locale.code)
routes = routes.concat(postsData.data.transPosts.map((post) => {
return {
route: `${locale.code === DEFAULT_LOCALE ? '' : '/' + locale.code}/blog/${post.slug}`,
payload: post
}
}))
}
return routes
}
}
//...
}
由于payload
在中可用context
,您可以重构BlogPost
组件中的 asyncData 函数以从中获取特定帖子context.payload
const getSinglePostFromContext = async ({ app, route, store, payload }) => {
if (payload) {
return payload
}
const postsData = await app.$blogClient.getSinglePost(
route.params.slug, store.$i18n.locale
)
return postsData.data.transPosts[0]
}
export default {
name: 'BlogPost',
async asyncData (context) {
const singlePost = await getSinglePostFromContext(context)
await context.store.dispatch(
'i18n/setRouteParams',
Object.fromEntries(singlePost.post.transPosts.map(
el => [el.lang, { slug: el.slug }])
)
)
return { post: singlePost }
},
// ...
}
nuxt build && nuxt export
再次运行
ℹ Generating pages
✔ Generated /it/
✔ Generated /es/
✔ Generated /
✔ Generated /blog/hello-world
✔ Generated /it/blog/ciao-mondo
✔ Generated /es/blog/hola-mundo
ℹ Ready to run nuxt serve or deploy dist/ directory
✨ Done in 33.82s.
现在 Nuxt.js 能够生成动态路由🎉
您可以使用以下方式测试静态站点的安装
nuxt serve
有时您可能需要为动态路由配置自定义路径,例如,您可能希望保留/blog/:slug
英语路径、/artículos/:slug
西班牙语路由和/articoli/:slug
意大利语路由。按照 nuxt-i18n 文档,您需要i18n
在nuxt.config.js
i18n {
// ...
parsePages: false,
pages: {
'blog/_slug': {
it: '/articoli/:slug',
es: '/artículos/:slug',
en: '/blog/:slug'
}
},
// ...
}
为了使这些设置在i18n
配置和generate
功能上可重复使用,请将自定义路由移动到单独的文件中i18n.config.js
export default {
pages: {
'blog/_slug': {
it: '/articoli/:slug',
es: '/artículos/:slug',
en: '/blog/:slug'
}
}
}
并将其导入nuxt.config.js
import i18nConfig from './i18n.config'
// ...
export default {
// ...
i18n: {
locales: LOCALES,
defaultLocale: DEFAULT_LOCALE,
parsePages: false,
pages: i18nConfig.pages,
encodePaths: false,
vueI18n: {
fallbackLocale: DEFAULT_LOCALE,
// ...
}
},
// ...
现在您可以重写generate
函数从自定义配置中获取正确的路径
routes: async () => {
const client = new BlogClient()
let routes = []
let postsData = []
for (const locale of LOCALES) {
postsData = await client.getAllPosts(locale.code)
routes = routes.concat(postsData.data.transPosts.map((post) => {
return {
route: `${locale.code === DEFAULT_LOCALE ? '' : '/' + locale.code}${i18nConfig.pages['blog/_slug'][locale.code].replace(':slug', post.slug)}`,
payload: post
}
}))
}
return routes
}
再次构建并导出所有内容,您将获得
ℹ Generating pages
✔ Generated /blog/hello-world
✔ Generated /it/articoli/ciao-mondo
✔ Generated /es/artículos/hola-mundo
✔ Generated /es/
✔ Generated /it/
✔ Generated /
ℹ Ready to run nuxt serve or deploy dist/ directory
✨ Done in 33.82s.
您的具有自定义路径的完整静态生成博客已准备就绪🎉
你可以做更多
在这个仓库中,你可以看到本教程的完整代码,最终部署在Netlify CDN 的https://eager-shockley-a415b7.netlify.app/上。Netlify 是我最喜欢的服务之一,它为静态网站提供云托管,提供持续部署、免费 SSL、无服务器功能等等……最终代码为网站添加了一些缺失的功能,例如,它增加了作者支持,使用了一些此处为简单起见省略的外部组件,并为项目启用了 SEO 选项,以便向页面添加元数据(请参阅nuxt-18n 文档中的 SEO 部分)。
最终代码中包含的另一个有用的东西是站点地图,它由Nuxt.js Sitemap 模块提供。站点地图的设置非常简单,因为它默认使用值,因此动态路由将自动包含在内。配置非常简单,只需在文件数组部分的末尾generate.routes
添加即可。@nuxtjs/sitemap
modules
nuxt.config.js
{
modules: [
// ...
'@nuxtjs/sitemap'
],
}
并配置sitemap
部分
export default {
// ...
sitemap: {
hostname: BASE_URL,
gzip: true,
i18n: DEFAULT_LOCALE
}
// ...
}
查看Github 上的 Nuxt 社区组织,了解更多精彩模块和项目!
祝你编码愉快!💚
封面图片由Marco Verch (CC BY 2.0)提供
鏂囩珷鏉ユ簮锛�https://dev.to/astagi/how-to-build-a-jamstack-multi-language-blog-with-nuxt-js-3gah