Next从0到1搭建高性能web应用

876 阅读7分钟

我正在参与掘金创作者训练营第5期, 点击了解活动详情

前言

如何搭建一个React SSR项目?我想有多种方案:既可以用诸如express、Koa、egg之类的基础的Node框架来搭建SSR应用;也可以使用更加专业的Next、Remix;但我选择了Next,它的star数量已经很多了,最重要的是它的社区非常活跃,遇到问题容易得到解决;Next对初学者并不友好,需要通过实践总结一些经验,因此今天我就讲一讲如何从0到1搭建一个高性能web应用,我们需要完善的需求如下:

  • 配置项目eslint规范以及项目格式化
  • tailwind + sass实现响应式页面布局
  • SEO:每个页面自定义TDK,没有设置的使用默认的TDK
  • 为每个页面设置统一的菜单栏和底部版权

在完善每一个需求点的过程中我会把一些用法和需要注意的地方讲解清楚,让大家少走弯路;

生成项目

首先利用next脚手架create-next-app生成一个项目

yarn create next-app --typescript

生成的目录结构如图所示 image.png 我们发现这个脚手架默认使用的包管理工具是yarn,因此我们在开发过程中尽量不要用npm,就用yarn进行包的安装和项目启动

然后运行yarn dev,项目默认启动在3000端口,打开localhost:3000,这样项目就搭建完成了,效果就是一个Next介绍页:

image.png

eslint与格式化

next/core-web-vitals

Next的eslint规则有很多,我们选择next/core-web-vitals,它是一个高性能的eslint标准;在项目初始化的时候已经默认是这个配置了,我们来看一看这个配置里面有哪些规则:

image.png

其中最重要的是上图中框出来的两条规则:不允许使用原生的a和img标签,需要使用next封装的Link组件和Image组件,其中Link组件带来的好处是不会整个页面刷新,并且会对链接到的页面进行预获取;但是Link组件有以下特性:

  • chilren只允许一个标签
  • children为文本时,会默认渲染为a标签
<Link href='/goods'>goods</Link>
// 会渲染为
<a href='/goods'>goods</a>
  • chilren为标签时,Link组件只执行跳转,而不渲染为标签
<Link href='/goods'><span>goods</span></Link>
// 会渲染为
<span>goods</span>
  • children为a标签时,会透传href,也就是a标签也会设置上href属性,这是我们想要的
<Link href='/goods'><a>goods</a></Link>
// 会渲染为
<a href='/goods'>goods</a>
  • 设置className、style无效

而我们如果想要让爬虫循环爬取内容,必须要设置a链接,爬虫不认脚本;所以我们采取统一策略:Link嵌套a标签,这样既保留了Link的优势又能够自由设置样式

而Image标签则是这样渲染的:

image.png

Image组件中的图片只有在视图之内才会渲染,这样极大地提高了页面性能;除此之外Image组件会判断我们的图片路径,如果是本地图片会自动进行图片大小的计算;而远程的图片则我们必须指定width和height,否则报错,这是为了让图片提前占位,否则图片一开始是0的高度加载之后高度变大,会造成CLS(布局偏移),影响用户体验;

Image组件直接设置类名和样式也是不行的,会透传到最里面的img,达不到我们想要的效果;因此我们只能用div包裹一层,然后给div设置样式

我们发现这个eslint规则是专门针对next的一些优化的,而对于原生的js语法方面并没有什么约束,所以仅仅使用这一条规则是不够的,所以我们还要添加eslint:recommended来规范js代码;另外还要添加一个有关格式化的eslint规则,为我们后面的格式化配置打下基础:

yarn add --dev eslint-config-prettier

eslint配置仅供参考:

{
  "root": true,
  "extends": ["eslint:recommended", "next/core-web-vitals", "prettier"],
  "rules": {
    "indent": ["error", 2],
    "semi": ["error", "never"],
    "function-paren-newline": ["error", "consistent"],
    "max-len": [
      "error",
      {
        "code": 140,
        "ignoreTrailingComments": true,
        "ignoreStrings": true,
        "ignoreTemplateLiterals": true,
        "ignoreUrls": true,
        "ignoreComments": true
      }
    ],
    "max-lines": ["error", 480],
    "no-multi-spaces": "error",
    "no-trailing-spaces": "error",
    "no-multiple-empty-lines": ["error"]
  }
}

接下来就是格式化,格式化和我们之前的eslint配置息息相关

格式化

js格式化只需要设置为保存时触发eslint修复即可;但是css格式化需要靠主动格式化才能进行,所以我们仍然设置一下各种文件的主动格式化的处理程序:

{
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true,
  },
  "[typescript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[typescriptreact]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[javascript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[jsonc]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[json]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[css]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[scss]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  }
}

tailwind + sass实现响应式页面布局

css这一块需要看具体做的什么业务,如果是中后台业务,那么引入antd之后再配置一下tailwind就可以了;如果是c端业务我们可以借助tailwind实现响应式,再利用sass(css modules)自定义样式,这次我们就以c端场景为例

tailwind配置

首先安装tailwind并生成tailwind配置文件:

yarn add tailwindcss

npx tailwindcss init -p

然后配置一下content为我们的代码路径

/** @type {import('tailwindcss').Config} */
module.exports = {
  // 移除没有使用的css
  content: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'],
  theme: {
    extend: {},
  },
  plugins: [],
}


最后是引入tailwindcss,可以在globals.css中引入我们的tailwind,先把里面的样式全部删掉然后加上tailwind:

/* ./styles/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

随便写一个组件测试一下:

function Goods() {
  return (
    <div className="flex items-center">
      <span className="text-ellipsis">
      123
      </span>
    </div>
  )
}

export default Goods

发现已经有tailwind提供的样式了:

image.png

tailwind实现响应式

tailwind实现响应式主要是通过媒体查询,主要是这几个类名:sm,md,lg,xl,2xl

我们拷贝tailwind的案例到我们的项目中:

import Image from "next/image"

function Goods() {
  return (
    <div className="max-w-md mx-auto bg-white rounded-xl shadow-md overflow-hidden md:max-w-2xl">
      <div className="md:flex">
        <div className="md:shrink-0">
          <Image className="h-48 w-full object-cover md:h-full md:w-48" src="https://images.unsplash.com/photo-1637734433731-621aca1c8cb6?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=404&q=80" width={202} height={192} alt="Man looking at item at a store"/>
        </div>
        <div className="p-8">
          <div className="uppercase tracking-wide text-sm text-indigo-500 font-semibold">Case study</div>
          <a href="#" className="block mt-1 text-lg leading-tight font-medium text-black hover:underline">Finding customers for your new business</a>
          <p className="mt-2 text-slate-500">Getting a new business off the ground is a lot of hard work. Here are five ideas you can use to find your first customers.</p>
        </div>
      </div>
    </div>
  )
}

export default Goods

结果项目跑不起来,因为next严格限制了image加载的域名,需要进行配置:

images:{
    domains:['images.unsplash.com']
}

重新运行next,我们拖动控制台之后,页面能够实现响应式;页面的响应式需要设计师提供多套样式,然后根据相应的设备宽度设置对应样式即可

SEO优化

要设置默认的TDK,我们可以先提取一个Layout组件出来,通过这个组件渲染一些所有组件共有的内容

import Head from "next/head"
import { FunctionComponent, ReactNode } from "react"
import styles from "./index.module.scss"

interface LayoutProps {
    children:ReactNode
}

const Layout: FunctionComponent<LayoutProps> = ({children}) => {
  return (
    <div className={styles.layout}>
      <Head>
        <title>Next从0到1搭建高性能web应用</title>
        <meta name="keywords" content="服务端渲染,高性能,web应用,next" />
        <meta name="description" content="配置项目eslint规范以及项目格式化,tailwind + sass实现响应式页面布局,SEO:每个页面自定义TDK,没有设置的使用默认的TDK,为每个页面设置统一的菜单栏和底部版权" />
      </Head>
      {children}
    </div>
  )
}

export default Layout

然后在_app.tsx中引入:

import '../styles/globals.css'
import type { AppProps } from 'next/app'
import Layout from '../components/layout'

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <Layout>
      <Component {...pageProps} />
    </Layout>
  )
}

export default MyApp

这样页面默认兜底的TDK就设置完成了,其他路由页面的TDK设置和Layout一样只要引入Head组件然后设置title、keywords、description就可以了

菜单栏和底部版权

菜单栏和底部版权是整个网站的公有部分,所以适合写在Layout组件中,我们改一下Layout组件,引入Nav组件和Footer组件并且为中间的children包裹一层main,用来控制内容区域的样式:

import Head from "next/head"
import { FunctionComponent, ReactNode } from "react"
import Footer from "./components/footer"
import Nav from "./components/nav"
import styles from "./index.module.scss"

interface LayoutProps {
    children:ReactNode
}

const Layout: FunctionComponent<LayoutProps> = ({children}) => {
  return (
    <>
      <Head>
        <title>Next从0到1搭建高性能web应用</title>
        <meta name="keywords" content="服务端渲染,高性能,web应用,next" />
        <meta name="description" content="配置项目eslint规范以及项目格式化,tailwind + sass实现响应式页面布局,SEO:每个页面自定义TDK,没有设置的使用默认的TDK,为每个页面设置统一的菜单栏和底部版权" />
      </Head>
      <Nav/>
      <main className={styles.layout}>
        {children}
      </main>
      <Footer/>
    </>
  )
}

export default Layout

总结

回顾一下我们的操作流程:首先我们利用cli搭建了一个简易的next项目,然后对eslint以及格式化进行配置;再进行tailwind的集成以及sass的css module的配置;最后我们抽离一个公共的Layout组件,用来设置TDK和头部导航栏以及底部版权,在这个基础上我们就可以开始写业务了;

点击这里查看源代码