一、自定义App
1. 特性
Next.js 使用 App 组件来初始化页面。你可以覆盖该 App 组件并控制页面的初始化。这让你可以做一些操作,例如:
- 页面切换之间保持布局的持久化
- 其实就是在各页面之间保持部分布局始终存在,类似于之前在React根组件中写的tabbar、或者是公共的导航条之类的布局,并不会随着页面的切换而丢失这些布局。
- 切换页面时保持状态(state)
- 管理全局数据。
- 使用 componentDidCatch 自定义错误处理
- 向页面(pages)注入额外的数据
- 添加全局 CSS 样式文件
- 该文件可以导入自定义全局样式文件、第三方组件库样式文件
2. 配置公共布局及全局样式
- 首先创建
./pages/_app.jsx
文件,添加如下代码:import "../app/globals.css"; // 引入自定义全局样式文件,样式即可全局生效 // 自定义App组件,类似于传统的React项目中的根组件 function App({ Component, pageProps }) { return ( <> {/*添加公共导航:在每个 page 页面配置导航标题,那么公共导航就可以根据页面的title属性,配置当前导航内容。*/} <div className="nav"> {pageProps.title ? pageProps.title : "公共导航"} </div> <Component {...pageProps} data="额外的数据" /> </> ); } // 这个方法使得每一个页面都由服务器渲染,禁用了自动静态优化功能,所以只有应用中每个单独页面都有数据请求时才可以使用。 // App.getInitialProps = async (appContext) => { // const appProps = await App.getInitialProps(appContext) // return { ...appProps } // } // 注意事项: // 1. 在 App 中添加自定义的 getInitialProps 时将禁用掉自动静态优化 // 2. 在自定义的 App 中添加 getInitialProps 时,必须添加 import App from "next/app" 语句、 在 getInitialProps 内部调用 App.getInitialProps(appContext) 以及将返回的对象合并到返回值中 export default App;
globals.css
全局文件需要在_app.jsx
文件中导入才能生效。- Component 参数即当前展示的 page 页面,因此,每当你在路由之间切换时,Component 都会更新为新的 page。因此,你传递给 Component 的任何属性都将会被 page 接收到。
- pageProps 参数是当前 page 页面,在 getServerSideProps 或者 getStaticProps 中请求到的数据,需要通过 {…pageProps} 的方式重新注入到当前 page 即:Component 组件。这有点类似于 React 高阶组件的用法。
- 同时可以通过自定义prop属性(比如: data)向 page 页面注入额外的数据,供所有的 page 使用。
注意事项
- _app.jsx 文件中的全局样式,针对 ./pages 目录 以及 `./app/page.jsx 首页文件都可以正常生效。
- _app.jsx 文件中的公共布局页面,针对 ./pages 目录 下的所有页面都生效,但是 `./app/page.jsx 首页不生效,并没有出现公共导航页面内容。
- 如果你对 App 组件进行了自定义时你的应用程序正在运行中,那么你需要重新启动开发服务器才能使修改生效。
- 目前,App 不支持 Next.js 的 数据获取方法,例如 getStaticProps 或 getServerSideProps。
截图如下
app/layout.js
也导入了app/global.css
文件,但是这里不是全局生效的,只针对app/page.jsx
内部的元素生效,pages 下的组件不生效。
二、自定义Document
在 next 中,我们只需要关心页面的 body 内容部分即可,而不需要关心
<html>
、<head>
这些标签的内容,next 服务端自动生成的一套统一的html + head + body
结构就能满足开发需求。如果项目中需要向head
头部加入一些其它的 css 文件或者是 js 文件,就需要通过自定义 _document.jsx 的方式实现,覆盖默认的文档结构。
因此,自定义 _app.jsx 是覆盖 body 中的内容,而 _document.jsx 则是对 head 和 html 元素的内容进行覆盖。
用法
- 新建
./pages/_document.jsx
文件,并添加内容:import Document, { Html, Head, Main, NextScript } from 'next/document' class MyDocument extends Document { static async getInitialProps(ctx) { const initialProps = await Document.getInitialProps(ctx) return { ...initialProps } } render() { return ( <Html> <Head> <meta charSet="utf-8" /> <meta name="viewport" content="initial-scale=1.0, width=device-width" /> <title>测试一下</title> </Head> <body> <Main /> <NextScript /> </body> </Html> ) } } export default MyDocument
- 要正确渲染页面,
<Html>
、<Head/>
、<Main/>
和<NextScript/>
是必须要引入。 - 需要在
yarn build
之后,通过yarn start
才能看到自定义的文档内容,dev模式是看不到刚刚自定义的document内容的。 - 在
pages/_document.js
中添加<title>
标签将导致next/head
出现意外结果,因为_document.js仅在初始预渲染时渲染。官方不建议把<title>
放到_document.js
中。 - page 页面的 title 标签,可以放在
_app.jsx
中统一配置:import Head from "next/head"; function App({ Component, pageProps }) { return ( <> <Head> <title>next学习</title> </Head> <Component {...pageProps} title="额外的数据" /> </> ); } export default App;
app/page.jsx
首页文件的<title>
标题可以在根布局文件app/layout.js
文件中配置export const metadata = { title: '首页', description: 'Generated by create next app', } export default function RootLayout({ children }) { return ( <html lang="en"> <body>{children}</body> </html> ) }
- 要正确渲染页面,
三、自定义404页面
- 在
pages
目录下新建404.jsx
文件,文件名为固定,当存在不合法的路径时,就会返回该页面中的组件内容。export default function NotFound() { return <div>404啦</div> }
四、自定义开发服务器
通过自定义代理服务器,解决跨域问题。
- 安装 express 和 http-proxy-middleware 包
- yarn add express http-proxy-middleware
- 在根目录下新建 server.js 文件,并添加如下内容
const express = require("express"); const next = require("next"); const { createProxyMiddleware } = require("http-proxy-middleware"); const devProxy = { "/api": { target: "目标地址", changeOrigin: true }, }; const dev = process.env.NODE_ENV !== "production"; const app = next({ dev }); const handle = app.getRequestHandler(); app.prepare().then(() => { const app = express(); if (dev && devProxy) Object.keys(devProxy).forEach(function (context) { app.use(createProxyMiddleware(context, devProxy[context])); }); app.all("*", (req, res) => handle(req, res)); app.listen(3000, (err) => { if (err) throw err; console.log(`> Ready on http://localhost:3000`); }); });
- 修改 package.json 文件
"dev": "node server.js",
- 重启服务器