自定义服务
Nextjs
提供了一个高级特性,它为我们提供了自定义服务器的能力,这使得我们可以在需要时创建自定义路由和处理请求。下面是一个简单的 Next.j
自定义服务器示例,演示了如何创建一个自定义路由:
当你需要在 Next.js 中创建自定义服务器并添加自定义路由时,以下是一些详细步骤:
-
创建 Next.js 应用:
首先,确保你已经创建了一个
Next.js
应用。你可以使用以下命令创建一个新的 Next.js 应用:bash$ npx create-next-app my-custom-app $ cd my-custom-app $ yarn
这将创建一个基本的
Next.js
应用,并安装所需的依赖项。 -
创建自定义服务器文件:
在
Next.js
应用的根目录下,创建一个名为server.js
的文件,用于定义自定义服务器的行为。 -
导入依赖项:
在
server.js
文件中,首先导入必要的依赖项,包括express
和next
:javascriptconst express = require('express'); const next = require('next');
-
配置 Next.js 应用:
使用
next
创建Next.js
应用的实例,并根据开发或生产环境进行配置:javascriptconst dev = process.env.NODE_ENV !== 'production'; const app = next({ dev }); const handle = app.getRequestHandler();
这些配置选项将告诉
Next.js
应用是否运行在开发模式下,以及如何处理请求。 -
准备应用程序:
使用
app.prepare()
方法准备Next.js
应用程序以响应请求:javascriptapp.prepare().then(() => { // 在这里定义服务器路由 });
这将确保
Next.js
应用程序准备好处理请求。 -
定义自定义路由:
在
server.js
文件中,可以使用Express.js
来定义自定义路由。例如,下面的代码定义了一个/custom-route
路由,它返回一个简单的文本响应:javascriptconst server = express(); server.get('/custom-route', (req, res) => { // 在这里处理自定义路由的请求 res.send('This is a custom route'); });
你可以根据需要定义多个自定义路由。
-
默认路由处理:
在自定义路由之后,需要定义一个默认路由,以便处理所有其他请求。通常,这个默认路由会调用
Next.js
的getRequestHandler()
方法:javascriptserver.get('*', (req, res) => { return handle(req, res); });
这确保了
Next.js
应用程序可以处理剩余的路由。 -
启动服务器:
最后,使用
server.listen()
启动服务器,并指定要监听的端口:javascriptserver.listen(3000, (err) => { if (err) throw err; console.log('> Ready on http://localhost:3000'); });
这将启动自定义服务器,并在端口
3000
上监听请求。 -
运行自定义服务器:
现在,你可以在终端中运行自定义服务器:
bash$ npm start
访问
http://localhost:3000/custom-route
将会看到This is a custom route
的响应。
这就是如何在 Next.js
中创建一个自定义服务器和自定义路由的详细步骤。通过这种方式,你可以为你的 Next.js
应用程序添加自定义路由和其他自定义功能,以满足特定的需求。请记住,在大多数情况下,使用内置的 Next.js
路由机制足以满足需求,只有在需要更高度定制的情况下才需要自定义服务器。
实战演练
接下来我们结合axios
来实现一个简单的项目实战。我们在上一节的基础上进行改造。 打开终端执行命令
bash
#新建server目录,并新增app文件
$ mkdir server
$ cd server
$ touch app.ts
#新建utils目录,并新增index文件和axios文件
$ cd ..
$ mkdir utils
$ cd utils
$ touch index.ts
$ touch axios.ts
#新建services目录,并新增index文件和type文件
$ cd ..
$ mkdir services
$ cd services
$ touch index.ts
$ touch type.ts
#新建一个nodemon的配置文件
$ cd ..
$ mkdir nodemon.json
执行完命令之后回到根目录下执行项目结构是这样的:
bash
$ tree -I 'node_modules|.next'
├── Dockerfile
├── README.md
├── next-env.d.ts
├── next.config.js
├── package.json
├── pages
│ ├── _app.tsx
│ ├── _document.tsx
│ ├── api
│ │ └── hello.ts
│ └── index.tsx
├── public
│ ├── favicon.ico
│ ├── next.svg
│ └── vercel.svg
├── server
│ └── app.ts
├── services
│ └──index.ts
│ └──type.ts
├── styles
│ ├── Home.module.css
│ └── globals.css
├── tsconfig.json
├── utils
│ ├── axios.ts
│ └── index.ts
└── yarn.lock
app.ts文件使用express自定义服务
ts
import express, { Request, Response, NextFunction } from 'express';
import next from 'next';
import portfinder from 'portfinder';
import chalk from 'chalk';
const basePort = 3000;
const port = process.env.PORT ? parseInt(process.env.PORT, 10) : basePort; // e.g: PORT=3000 yarn dev
portfinder.basePort = port;
const dev = process.env.NODE_ENV !== 'production';
portfinder.getPort(function (error: Error, nextPort: number) {
if (error) {
console.error(error);
return;
}
if (port !== nextPort) {
process.env.PORT = nextPort + '';
console.info(
`${chalk.yellow(
'warn',
)} - Port ${port} is in use, trying ${nextPort} instead.`,
);
}
const app = next({ dev, port: nextPort });
const handle = app.getRequestHandler();
app
.prepare()
.then(async () => {
const server = express();
process.env.PORT = nextPort.toString();
server.all('*', (req: Request, res: Response, next: NextFunction) => {
if (/^\/api\/.*/.test(req.url)) {
res.json({msg:'Hello Warld'})
} else {
return handle(req, res);
}
});
server.listen(nextPort, () =>
console.info(
`${chalk.green(
'ready',
)} - started server on 0.0.0.0:${nextPort}, url: http://localhost:${nextPort}`,
),
);
})
.catch((error: Error) => {
console.error(error);
});
});
我们使用express
进行了自定义服务,并对以/api
开头的请求进行了自定义处理,其他路径的请求交于next
进行处理。 按照依赖包
bash
$ yarn add express portfinder chalk nodemon
$ yarn add ts-node
新增nodemon
配置文件
json
{
"watch": ["server/**/*"],
"ext": "ts",
"exec": "ts-node server/app.ts"
}
执行脚本 yarn dev
bash
$ yarn dev
使用postman
进行验证一下

好了,到此为止自定义服务基本完成了。
接下来我们自定义一个axios
请求,主要目的是为了简化异步请求逻辑。
在新增的utils/axios
文件下编写以下代码。
ts
import axioslib, { Method, AxiosHeaders } from 'axios';
export interface BaseResult<T> {
success: boolean;
t: number;
code?: number;
result: T;
msg?: string;
}
export const DEVBASEURL = 'http://localhost:3000'; //默认开发环境的baseURL
const DEFAULT_AXIOS_TIMEOUT = 15000; //超时时间
export const axios = axioslib.create({
timeout: DEFAULT_AXIOS_TIMEOUT,
baseURL: DEVBASEURL
});
// 生成FormData
function createFormData<F>(forData: F): FormData | null {
if (!forData) {
return null;
}
const data = new FormData();
Object.entries(forData).forEach(([name, value]) => {
data.append(name, value as string);
});
return data;
}
// 处理url中的path参数
function pathReplace<P>(url: string, path: P): string {
if (!path) {
return url;
}
let nextApi = url;
Object.entries(path).forEach(([name, value]) => {
const encodedValue = encodeURIComponent(value + '');
nextApi = nextApi
.replace(`{${name}}`, encodedValue)
.replace(`:${name}`, encodedValue);
});
return nextApi;
}
// P代表path参数,Q代表query参数,B代表body参数,H代表headers参数,F代表formData参数,R代表返回值
export default async function ajax<
P,
Q,
B,
H extends AxiosHeaders | null,
F,
R,
>(
method: Method,
url: string,
path: P,
query: Q,
body: B,
headers: H,
formData: F,
): Promise<R> {
const response = (
await axios({
method,
url: `/api${pathReplace(url, path)}`,
params: query,
data: body || createFormData(formData),
headers: headers || undefined,
})
).data;
if (!response.success) {
throw new Error(response.msg || '请求失败');
}
return response;
}
添加axios
bash
$ yarn add axios
index
文件中导出axios
实例
js
// utils/axios
export { default as axios } from './axios';
最基本的axios
我们已经处理完毕,现在让我们来添加一个api
接口
打开services
文件夹的index.ts
文件开始编写我们第一个接口
ts
// services/index.ts
import ajax, { BaseResult } from '../utils/axios';
import * as Type from './type';
class Services {
public static getUserInfo(
query: Type.GetUserInfo$Query,
): Promise<BaseResult<Type.GetUserInfo$Response>> {
return ajax('GET', '/user/info', null, query, null, null, null);
}
}
export default Services;
继续type.ts
文件定义给个参数的ts
类型
ts
export interface GetUserInfo$Query {
id: string;
}
export interface GetUserInfo$Response {
data: {
name: string;
age: number;
};
}
客户端的工作我们已经处理完毕了,加下来我们要去编写接口逻辑了,切换到server目录下的app.ts。我们编写我们的第一个后台接口。
ts
//server/app.ts
import express, { Request, Response } from 'express';
import next from 'next';
import portfinder from 'portfinder';
import chalk from 'chalk';
+ const { parse } = require('url');
const basePort = 3000;
const port = process.env.PORT ? parseInt(process.env.PORT, 10) : basePort; // e.g: PORT=3000 yarn dev
portfinder.basePort = port;
const dev = process.env.NODE_ENV !== 'production';
portfinder.getPort(function (error: Error, nextPort: number) {
if (error) {
console.error(error);
return;
}
if (port !== nextPort) {
process.env.PORT = nextPort + '';
console.info(
`${chalk.yellow(
'warn',
)} - Port ${port} is in use, trying ${nextPort} instead.`,
);
}
const app = next({ dev, port: nextPort });
const handle = app.getRequestHandler();
app
.prepare()
.then(async () => {
const server = express();
process.env.PORT = nextPort.toString();
server.all('*', (req: Request, res: Response) => {
if (/^\/api\/.*/.test(req.url)) {
// 拦截以 /api/ 开头的请求,做自定义处理
_ res.json({ msg: 'Hello World!' });
+ const parsedUrl = parse(req.url, true);
+ const { pathname, query } = parsedUrl;
+ if (pathname === '/api/user/info') {
+ if (query?.id === '1') {
+ res.json({ name: '张三', age: 18, success: true });
+ } else {
+ res.json({ msg: '没有找到', success: false });
+ }
+ } else {
+ res.json({ msg: 'Hello World!' });
+ }
} else {
// 对其他请求直接返回页面内容
return handle(req, res);
}
});
server.listen(nextPort, () =>
console.info(
`${chalk.green(
'ready',
)} - started server on 0.0.0.0:${nextPort}, url: http://localhost:${nextPort}`,
),
);
})
.catch((error: Error) => {
console.error(error);
});
});
按照处理路径的parse
bash
$ yarn add parse
接口已经编写完毕,我们来调用一下这个接口。切换到page/index.tsx
,删除多余代码后
ts
import Services from 'services';
export default function Home() {
return (
<>
<button
onClick={async () => {
await Services.getUserInfo({ id: '1' });
}}
>
按钮
</button>
</>
);
}
执行命令yarn dev
,打开http://localhost:3000,点击按钮
,打开控制台看看

好的,到此为止你已经基本使用next
自定义服务开发接口了。 下一节我们正式踏入web
服务构建。