Next.js 服务端渲染项目实战(上)——2021 最新版

2,541 阅读5分钟

nextjs

解决的问题

  • 你需要针对生产环境进行优化,例如代码拆分。
  • 对一些页面进行预先渲染以提高页面性能和 SEO。你可能还希望使用服务器端渲染或客户端渲染

优势: 适合React项目,相比其他服务端渲染的框架更为简单减少学习成本开箱即用

pages文件

pages 下的文件自动生成路由,pages/[id].js 生成动态路由

静态文件如图片可以放在根目录的 public 下面,对外可以直接访问 /

nextjs 提供自动刷新的功能,每次保存编译之后浏览器的页面会自动刷新,页面也会有每次轮询刷新的功能

预渲染

nextjs 预渲染每个页面,也就是会为每个页面是生成 HTML 文件,从而有更好的 SEO 效果

预渲染的方式

分成静态渲染和服务端渲染两种方式,区别在于静态渲染是在构建 HTML 的时候渲染,在每次页面请求的时候重新调用,服务端渲染是在每次发请求的时候渲染 html

当然也可以使用“两种方式混合渲染” , 更推荐静态渲染,有更好的效果

样式

项目中用到的样式有两种:

  • 全局样式

    • 使用之前要在\_app.js 里面引入全局就能使用了 import '@/styles/globals.scss' ,一些插件的样式需要自己引入的也是用这种方式
  • 组件的样式

    • nextjs 中支持 css 模块,文件命名为[name].module.css,使用的时候需要用引入文件点类名的方式 className={styles.error}

    • 支持 Scss ,不过要在 next.config.js 里面配置一些东西

      sassOptions: {
      includePaths: [path.join(__dirname, 'src/styles')],
    },
    
    import styled from "styled-components";
    
    const PageWrap = styled.div`
      margin: 10px 100px;
      border: 1px solid #f0f0f0;
      padding: 20px;
      min-height: calc(100vh - 118px);
    `;
    
    const Page: NextPage = () => <PageWrap>...把之前的组件包裹住 </PageWrap>;
    

因为我们项目在之前并没有用 CSS 模块,所以用 styled-components 迁移更方便容易,对于这两者的区别和选择可以看一下这篇文章《Css in Js 之 styled-components》

图片

nextjs 中的 image 组件对原生的做了扩展优化,无论加载多少图片都不会影响构建的速度,给图片加了懒加载的功能,图片进入视口才会加载

import Image from "next/image";

<Image src="/logo.png" alt="squid.io" width={500} height={500} />;

下面是 layout="fill" 也就是图片的宽高适应父元素的宽高,父元素必须有定位

<Image
  src="/assets/index/bestHelp/diverse-database.svg"
  alt="diverse-database"
  layout="fill" //表示和有定位的父盒子一样大小
  objectFit="contain"
/>

注意点

  • 图片资源最好放在 public 相当于根目录就可以直接引用图片文件路径了
  • 图片必须要给宽高或 layout="fill",以及 alt
  • 图片就会有默认样式 absolute,如果图片上面还有 absolute 的文字就会被遮盖住,把文字的层级加一下就行了 z-index

next/image 导出标签常用属性 必选 src width height alt 可选

  • layout 视口尺寸变化是图片布局的行为,参数 intrinsic 默认 缩小不放大,fixed 固定大小,responsive 缩小放大,fill 拉升与父元素一样大
  • quality 图片优化后质量 默认是 75
  • priority true 图片优先级最高,默认为 false
  • placeholder 占位, blur 模糊 empty 空白
  • style 代替 className
  • decoding 代替 async 更多属性 >

Script

引入一些脚本文件可以使用下面集中方法

import Head from "next/head";

<Head>
  <script async src="https://www.google-analytics.com/analytics.js" />
</Head>;
import Script from "next/script";

<Script src="https://www.google-analytics.com/analytics.js" />;

当想要在处理一些逻辑可以使用,比如广告脚本

<Script strategy="lazyOnload">
  {`处理逻辑`}
</Script>

// or

<Script
  dangerouslySetInnerHTML={{
    __html: `处理逻辑`
  }}
/>
<Script
  id="stripe-js"
  src="https://js.stripe.com/v3/"
  onLoad={() => {
    处理逻辑;
  }}
/>

更多 >

路由

nextjs 中的路由是放在 pages/文件夹里面就可以生成路由如pages/blog.tsx 的路由就是 /blog

动态路由可以在 pages 文件夹下面建立如 pages/[id].tsx 的文件,就可以路由到 /pages/112 等路由下面

路由跳转可以通过 next/router 里的 useRouter

import { useRouter } from "next/router";

function Blog() {
  const router = useRouter();

  router.push("/login");
}

常用属性:

  • pathname 路径
  • query 路径中的变量 更多 >

静态路由跳转可以使用 next/link

import Link from "next/link";

<Link href="/blog?id=1" replace>
  <a>Link</a>
</Link>;

注意

  • link 标签下面要是纯文字或是 a 标签,否则会有警告
  • "Multiple children were passed to"link 标签里的子元素必须有一个父元素

配置国际化

  1. next.config.js 文件里的配置
// next.config.js
module.exports = {
  i18n: {
    locals: ["en", "zh"], //支持语言
    defaultLocale: "en", // 默认的语言
    localeDetection: false, //自动区域检测,在访问路由的时候通过请求Accept-Language来自动检测用户的语言,并在路径中显示
  },
};
  1. \_app 里引入
import { appWithTranslation } from "next/i18n";

const App = ({ Component, pageProps }: AppProps): JSX.Element => (
  <Component {...pageProps} />
);

export default appWithTranslation(App);
  1. 导入国际化配置文件

public/locales/en/common.json public/locales/zh/common.json

  1. 在页面中请求国际化配置文件
import {serverSideTranslations} from next-i18n/serverSideTranslations

export const getStaticProps: GetStaticProps = async ({locale}) => ({
props: {
...(await serverSideTranslations(locale ?? 'zh',['common']))
}
})
  1. 在组件中使用
import { useTranslation } from 'next-i18next'

const {t} = useTranslation()

<div>{t('hello')}</div>

获取数据

getStaticProps 获取本地的数据

静态生成,在 build的时候获取数据,它是一个异步的方法

这是获取 public/locale/zh/common.json 翻译文件

const getStaticProps: GetStaticProps = aycnc ({locale}) => ({
	props: {
		...(await serverSideTranslation(locale ?? 'zh',['common']))
	}
})

配置根据环境自动切换的 url

  1. public/config/base-url.json 下配置默认访问的路径
{
  "config": "http://localhost:3000"
}
  1. fetcher 请求到上面的 json 数据并返回它
export type BaseUrlConfig = {
  console: string
}

const getBaseUrl = async (): Promise<BaseUrlConfig> => {
  const baseUrl: BaseUrlConfig = { console: '' }

  try {
    const res = await fetch('/config/base-url.json')
    const config = (await res.json()) as BaseUrlConfig

    return config
  } catch (error) {
    console.error(error)
  }

  return baseUrl
}

export default getBaseUrl
  1. 封装一个请求这个地址的 hook
import useSWR from "swr";

import getBaseUrl, { BaseUrlConfig } from "@/utils/getBaseUrl";

const useConfigUrl = (): BaseUrlConfig | undefined => {
  const { data } = useSWR("base-url", getBaseUrl);

  return data;
};

export default useConfigUrl;
  1. 在页面中使用
import useConfigUrl from "@/hooks/useConfigUrl";

const config = useConfigUrl();

next.config.js 配置

请求代理配置

遇到问题

  • 样式 写成 xxx.module.css 报错 Selector "&::after" is not pure (pure selectors must contain at least one local class or id)

    原因:在 import styles from './index.module.scss'引入样式文件之后要用文件名点类名才能使用className={styles.alertMes}

  • window is not define 原因:在 window 这个全局对象是运行在浏览器中的,在服务端渲染中是没有的,所以会报错

    解决方法

    1. 对于组件 可以使用动态导入(next/dynamic)的方式
    import dynamic from "next/dynamic";
    
    const WebHeader = dynamic(async () => import("@/components/WebHeader"));
    
    return <WebHeader />;
    
    1. 在使用 window 的地方可以添加条件判断
    if (typeof window !== "undefined") {
      return window.localStorage;
    }
    
  • nextjs 里面写的函数方法都要导出