前言
一直以来,所在公司的产品官网都是Spring MVC(thymeleaf模板)项目,前端用Idea打开java项目编写resources/template/xx.html文件,在另一个地方编写css、js等静态资源。
平时上网注意到很多网站官网都基于Nuxt开发(Vue devtool会点亮Nuxt),于是尝试Nuxt能否作为新的方案去落地公司/产品官网业务场景,关注它能实际带来什么;
本文以开发一个游戏官网为需求,从Nuxt初始化项目开始,介绍Nuxt开发核心概念。第一部分描述Nuxt并理解它,第二部分Nuxt SSG游戏官网实践;
github:github.com/KID-1912/nu...
预览地址:nuxt-tgame-web.vercel.app/ (基于vercel的SSR) kid-1912.github.io/nuxt-tgame-... (基于github page的SSG纯静态)
AD :掘金2024年度人气创作者 打榜快结束了,这里求各位看官支持点这里助我
理解Nuxt
我们对Nuxt官网描述进行概括(去掉限定词),得到一句话"Nuxt是一个全栈Web应用和网站的开源框架",尝试引出Nuxt的核心内容:
Nuxt中web全栈开发 = 前端开发 + 现代SSR (水合) + 基于nodejs的服务端开发
可以认为Nuxt所有内容归于以上3个部分,这也是为什么Nuxt文档看起来有点杂多的原因;不得不说,Nuxt的胃口很大,它希望做到当你进行一个不仅限于前端开发(无论spa/ssr),可能还需要服务端渲染,亦或是基于nodejs进行服务端开发,将以上都在一个名为Nuxt全栈框架中得到全部支持,并以一个项目作为整体来实现全部;
Nuxt特性
Nuxt框架提供了大量特性给我们开发项目,可分为
为前端开发提供的特性:
这部分特性其实都是Nuxt为前端开发作出的支持,本质上我们自己搭建的项目通过手动都能实现上述特性支持,所以不用太过关注,在官方文档了解后使用时再查即可;
为现代SSR提供的特性:
-
内置服务端渲染(无需配置服务器与手动注入数据)
这部分特性是使用Nuxt SSR时必要工具,useFetch这个组合函数是核心
为基于nodejs服务端提供的特性:
-
支持
/server
目录编写API层与中间件(基于Nitro服务器引擎) -
nuxt前端开发部分可通过
$fetch
直接调用server下API
这部分特性是Nuxt创建的web应用添加服务端能力,本文不会过多涉及;
其它特性:
- SSG: 静态网站生成,实现静态托管
为什么使用Nuxt?
不得不承认Nuxt确实做了很多,但我们必须思考并讨论目前需要它来干什么,以至于在开发中减少"我为什么要用使用它"的困惑,有以下4点:
基于nodejs服务端开发
在Nuxt的/server
目录可以为你的web应用编写API层和服务器处理程序(服务器路由/中间件);由于Nuxt的web应用最终将部署在nodejs上(SSG除外),那么Nuxt心想:"都nodejs了,不得上服务端开发支持啊!";
Nuxt服务端开发使用Nitro,从快速为web应用提供API支持来看是有可取之处,但如果需要完整的、独立的NodeJs服务端工程,显然Express/Koa/NestJs等更深入人心;况且在中小厂nodejs服务端开发场景较少,所以Nuxt这部分可以当做未来宏图吧,本文不过多涉及。
服务端渲染
SPA单页面应用?
由于前端已经完全进入js驱动页面的动态内容,vue/react让我们无需手动js操作dom更新界面,我们通过一个入口文件(main.js/ts)将任意模块集成到单个index.html,配合前端路由(router)实现SPA应用;
SPA最大特点是无需刷新/无重载页面,高度交互/动态,以此衍生很多优点......
SSR服务端渲染?
SPA应用的界面最终是在客户端即浏览器生成并渲染的(CSR),SSR即服务端生成(组装)好完整html页面内容响应到客户端,浏览器直接展示即可;它并不是Nuxt/Next带来的概念,不存在SPA和SSR谁比谁高级的说法,传统SSR本身就比SPA具备2大优点(当然也有不足于spa处):
- SEO(搜索引擎可抓取)
- 客户端首次加载速度快/白屏短(首屏渲染无需等待大量js加载与执行)
现代SSR!
Nuxt中提出通用渲染的SSR模式,简单来说就是以URL静态页面路径访问web应用时(location),页面内容来自是服务端某个时间生成并渲染的结果,这使得页面可被SEO;在html页面在浏览器展示后,支持用户点击切换页面等交互,表现为SPA方式以js更新界面(而不是传统ssr加载新页面),使SSR返回的静态页面在浏览器同样具有高交互性;
Nuxt通过让浏览器加载完整html时,在执行js中实现一个水合 的过程,使页面内容可动态;也就是我们在vue源码中经常见到的单词 hydrating
相关,此时页面交互表现和spa相同。
前端项目模板
我们完全可以不使用Nuxt的服务端开发和SSR能力,把它当做一个前端项目开发模板,它本身具备规范化内容,如预定义了不同作用的项目目录结构值得参考学习,以及plugins/modules拓展应用/项目的能力;
这使得团队可以通过一行 npx nuxi@latest init <project-name>
创建一个标准nuxt项目,团队对项目能力支持统一查阅nuxt官方文档;唯一弊端是需要自动摒弃文档上SSR相关的内容(不相干),并在nuxt配置中设置ssr: false
关闭ssr改用客户端渲染;最终同样通过npm run build
构建出SPA页面。
SSG
SSG(静态站点生成) :在构建阶段将网站的页面预渲染为纯静态 HTML 文件,用户访问时直接返回这些静态文件。简单来说,ssr是每个页面都是在服务端生成并返回的,SSG是在构建时就生成固定的html页面,相当于执行一次ssr渲染结果作为静态页,可直接作为纯静态资源托管部署;与ssr相比:
- 同样返回最终html文件,支持SEO
- 不需要ssr的基于nodejs服务器
- 页面数据/内容在生成时固化,不如ssr更加动态
- 无需服务端渲染,加载快,且无服务器压力
- 页面同样会在浏览器水合
通过Nuxt SSG 做到使用vue开发的页面,通过预渲染构建生成可被SEO的html页面(每个路由对应一个);整个过程纯前端 ,适用于页面内容不需频繁更新的情况,我认为在部分官网开发的场景很契合,本文将对该部分具体实践;
实现动态内容的SSG!
如何最大化实现SSG动态内容能力?SSG是预渲染好的页面,这注定了SSG页面内容的更新都需要一次构建并部署(上传静态资源即可);对于内容需更新的官网,我们开发一个简单的CMS内容管理系统(支持运营人员修改官网内容的后台),将动态内容的数据保存在数据库;
假设是ssr页面,你只需要开发页面时编写调用对应接口获取后台配置的内容数据 ,一旦部署后页面每次访问时将调用接口,返回最新内容页面;
对于SSG页面,同样你只需要开发页面时编写调用对应接口获取后台配置的内容数据 ,NuxtSSG构建时将调用接口,将返回数据作为此次生成的页面内容,因此每次运营人员后台修改完,需要重新构建并上传静态资源(或者运营人员保存时调用CI/CD的API自动构建并上传资源);
Nuxt官网实践
本项目参考了以下游戏/公司官网(设计,交互,内容):
腾讯游戏:game.qq.com/
大梦龙途:www.dodjoy.com/
王者国际服:www.honorofkings.com
创建Nuxt项目
shell
npx nuxi@latest init nuxt-tgame-web
layouts布局
大部分企业/公司官网下每个页面都有相同的页头(header/nav)页尾(footer)等:
编写页面基本结构所需要的Header/Footer组件,将他们作为Nuxt Layouts可重用布局的内容(类似admin项目的header/sidebar部分),代码见nuxt-tgame-web/layouts
默认布局
定义一个 /layouts/default.vue
默认布局文件,作为整个官网vue页面共同的界面:
html
// /layouts/default.vue
<script setup>
import Header from "./Header/Header.vue";
import Footer from "./Footer/Footer.vue";
</script>
<template>
<div class="default-layout">
<slot name="header">
<Header></Header>
</slot>
<slot></slot> // 页面内容将被插入此处
<slot name="footer">
<Footer></Footer>
</slot>
</div>
</template>
使用布局
在 /app.vue
中使用 NuxtLayout 内置组件包裹路由,NuxtLayout 将自动引入并解析 /lauyouts
目录下布局界面
html
// /app.vue
<template>
<NuxtLayout> // Nuxt内置布局组件
<NuxtPage /> // Nuxt内置路由组件,类似vue中router-view
</NuxtLayout>
</template>
Nuxt Layouts 额外提供了强大的自定义多布局和动态切换布局能力,更多能力见layouts
覆盖布局
为Nuxt项目提供了布局后,所有pages下的页面都将使用layouts下某个布局,你可以通过在页面(/pages/*.vue)下 definePageMeta
传递 layout: false
,对当前页面关闭布局,然后在此基础上支持自定义布局,如我的 /home/index.vue
由于需要自定义Footer样式,在页面代码关闭全部布局后覆写页面布局;覆盖布局具体使用见在每个页面覆写布局
页面与路由
Nuxt规定所有页面的视图文件以 /pages/**/\*.vue
存放在pages目录,并且默认Nuxt会自动根据pages目录结构生成vue页面路由,我们只需在app.vue声明路由
html
// /app.vue
<template>
<NuxtLayout>
<NuxtPage /> // Nuxt内置路由组件,类似vue中router-view
</NuxtLayout>
</template>
完整Nuxt页面&路由规则见 目录结构 pages
覆写/自定义路由
按照Nuxt页面/路由规则,我的官网pages目录结构应该是:
- news/index.vue:新闻资讯页
- product/index.vue:产品页
- index.vue 首页(根路径页面)
Nuxt会将pages下每个子目录的index.vue/ts/tsx等尝试解析为路由页面,并按照子目录路径与名称生成路由
但实际在我的官网实践,pages目录结构是:
- home/index.vue:首页
- news/index.vue:新闻资讯页
- product.index.vue:产品页
- index.vue 根路径页面(引入home/index.vue作为内容)
这是因为我关闭或者说覆写了Nuxt默认生成的路由,原因如下:
-
Nuxt中pages目录下所有目录与文件都会被尝试解析为路由,如果你为页面子目录新增components也会被解析为路由,这要求我们在pages目录不能存放非页面模块;
-
Nuxt SSG构建时也最遵循默认目录解析为路由的规则,pages下非页面内容很可能导致构建时的意外错误;
为了让pages目录内容自由化,通过手动声明Nuxt页面路由,实现对默认生成路由的覆写:
js
// /app/router.options.ts
import type { RouterConfig } from '@nuxt/schema'
export default {
routes: (_routes) => [
{
path: "/",
component: () => import("~/pages/index.vue"),
meta: { layout: false } // 取代definePageMetaData
},
{
path: "/news",
component: () => import("~/pages/news/index.vue"),
},
{
path: "/product",
component: () => import("~/pages/product/index.vue"),
},
],
} satisfies RouterConfig
注:当你自定义路由后,页面中类似definePageMetaData等方法将不再生效,需要手动指定
路由跳转
Nuxt中路由跳转,我们官网实践中主要通过NuxtLink内置组件导航到其它页,它将被渲染为a标签(SEO)
html
<!-- 网站导航 -->
<div class="tabs-nav flex">
<div class="tab-item">
<NuxtLink class="nav-link" to="/">首页</NuxtLink>
</div>
<div class="tab-item">
<NuxtLink class="nav-link" to="/product">产品信息</NuxtLink>
</div>
<div class="tab-item">
<NuxtLink class="nav-link" to="/news">最新资讯</NuxtLink>
</div>
// ......
</div>
当然,你也可以监听点击vue router手动跳转 router.push()
SSG
useFetch数据获取
Nuxt开发SSR页面和日常我们开发SPA页面几乎无太大差别,但如果你需要SSR服务端获取动态数据并为页面所用,那么你必须使用 useFetch
实现数据的API获取;
你可以理解在SPA应用中,我们通过浏览器发起请求调用数据API,useFetch则是在此基础上,兼容了服务端SSR获取数据,通过一个组合式函数实现双端都可获取数据,是通用渲染的必要能力;
以我们官网实践的新闻资讯页/pages/news/index.vue为例,这个页面的资讯列表数据,我们直接调用腾讯游戏官网的GET接口:
ts
<script lang="ts" setup>
type News = {
com_cover: string;
com_title: string;
// .....
};
type NewsResponseData = {
data: { items: News[] };
};
// 资讯列表
let newsList: News[] = [];
const api_url: string = "https://apps.game.qq.com/cmc/common_list";
const api_params = {
com_biz: 191,
com_type: 1,
// .....
pagesize: 18,
page: 1
};
try {
const { data } = await useFetch<NewsResponseData>(
api_url,
{ query: api_params }
);
newsList = data.value?.data.items || [];
} catch (error) {
console.warn(error);
}
</script>
<template>
<div class="wrap">
<div class="news-list flex flex-wrap justify-between">
// 遍历列表
<div v-for="news in newsList" :key="news.com_biz" class="news-box">
</div>
</div>
</div>
</template>
在SSR通用渲染返回页面时(通过url访问页面),nodejs服务器调用API或HTTP获取数据,生成页面后响应返回;
在浏览器从一个页面router方式跳转该页面时,表现同spa应用,由浏览器发起http请求;
更多useFetch细节见Nuxt useFetch
SSG与useFetch
Nuxt SSG构建时如何处理页面的useFetch?
答案是调用一次useFetch将返回数据作为生成此次静态页数据的依据,看起来像执行一次SSR后将页面"快照"输出(预渲染);
SSG生成
开发完全部页面,终于可以SSG生成最终静态页了,只需要一行命令:
shell
npm run generate
或
npx nuxi generate
将生成 dist
目录和 .output/public
目录,你可以在本地通过http-server
或其他方式开启一个静态服务本地预览;
SSG注意点
-
中文名称路径:SSG构建不允许项目路径存在中文,保证你的整个项目源文件在电脑上的绝对路径不允许含中文
-
根路径页面:SSG构建会爬取根路由页面和pages目录,请保证pages目录下必须存在pages/index.vue文件
SSG部署
SSG作为静态资源,支持在任何静态托管的服务上;github page、vercel 上传并部署
更好SEO
页面Head
Nuxt提供 useHead
组合式函数定义html页面head,同时可以通过它动态定义每个页面不同head(title、meta:keywords、meta:description)
History Path
应避免全路径或hash路径(/index.html)或hash路径链接跳转,使用history路由模式
语义化标签/属性:
使用NuxtLink或a标签,避免使用手动js跳转;合理使用文档结构标签(header/section/footer/nav)
部署
github page
利用github page的静态托管,将Nuxt SSG构建后的 .output/public 目录作为托管目录
vercel
将你的github项目导入到vercel后,vercel能自动识别到Nuxt项目,并采用Nuxt Preset预设,全程你无需任何配置deploy一个基于nodeJS的SSR web应用
其他
你可以在任何地方以Nuxt SSG部署静态资源,或者以NodeJS服务方式类似 node .output/server/index.mjs
实现SSR网站,更多见官方说明部署
todo
目前【资讯】页依赖腾讯游戏官网接口,由于官方做了保护目前是挂掉的,后续迁移为自己的接口
AD :掘金2024年度人气创作者 打榜快结束了,这里求各位看官支持,点这里助我