当NestJS遇上Next.js
Nest (NestJS) 是一个用于构建高效、可扩展的Node.js服务器端应用程序的开发框架。它利用 JavaScript 的渐进增强的能力,使用并完全支持TypeScript(仍然允许开发者使用纯 JavaScript 进行开发),并结合了 OOP (面向对象编程)、FP (函数式编程)和 FRP (函数响应式编程)。
Next.js是一个可用于生产环境的服务端渲染框架。
(ps:这两个框架名字长得还真像,可千万别搞错了)
NestJS更专注一服务端,而Next.js更专注也页面的渲染,如果将这两个框架结合在一起,岂不是完美?本文将介绍如何将这两个框架整合到一起使用。
初始化
首先,我们使用Nest-cli来初始化项目
nest new nest-next
等初始化完成,我们可以看到项目结构如下:
我们把项目跑起来,执行:yarn start,nest默认启动端口为3000,我们打开浏览器就能开到初始页面了
安装Next.js相关依赖
yarn add next react react-dom
yarn add cross-env ts-node-dev ts-node @types/react --dev
初始化Next.js相关配置
一、在项目根目录下创建pages文件夹,这是next默认存在页面的文件夹
二、在pages文件夹下创建第一个页面
/* index.tsx */
import React from 'react';
const Index = () => (
<h1>
这是Next渲染的压面
</h1>
)
export default Index;
三、在src目录下创建一个next文件夹,next相关的配置都会放在这里
创建一个service
/* next.service.ts */
import {
IncomingMessage,
ServerResponse
} from 'http';
export class NextService{
private app!: any;
public getApp(): any {
return this.app;
}
public setApp(app: any):void {
this.app = app;
}
public async render(page:string, req:IncomingMessage, res: ServerResponse):Promise<void>
public async render(page:string, data: any, req:IncomingMessage, res: ServerResponse):Promise<void>
public async render(page: string, arg2: any, arg3: any, arg4?: any):Promise<void> {
if(NextService.isIncomingMessage(arg2)){
await this.app.render(arg2, arg3, page);
}else{
await this.app.render(arg3, arg4, page, arg2);
}
}
private static isIncomingMessage(arg:any):arg is IncomingMessage{
return typeof arg.httpVersion === 'string';
}
}
创建一个middleware
/* next.middleware.ts */
import {Injectable, NestMiddleware} from '@nestjs/common';
import {
IncomingMessage,
ServerResponse
} from 'http';
import {NextService} from './next.service';
@Injectable()
export class NextMiddleware implements NestMiddleware{
constructor(
private readonly next: NextService
) {}
public use(req: IncomingMessage, res: ServerResponse) {
const handle = this.next.getApp().getRequestHandler();
handle(req, res);
}
}
创建一个module
/* next.module.ts */
import {Module} from '@nestjs/common';
import {NextService} from './next.service';
import next from 'next';
import {ServerConstructor} from 'next/dist/next-server/server/next-server';
type NextServerConstructor = Omit<ServerConstructor, 'staticMarkup'> & {
dev?: boolean
}
@Module({
providers: [NextService],
exports: [
NextService
]
})
export class NextModule{
constructor(
private readonly next: NextService
) {}
public async prepare(options?: NextServerConstructor) {
const app = next(Object.assign({
dev: process.env.NODE_ENV !== 'production',
dir: process.cwd(),
}, options || {}));
return app.prepare().then(()=>this.next.setApp(app));
}
}
将NextModule注入到Nest中,我们来修改src/app.module.ts文件
/* app.module.ts */
import { MiddlewareConsumer, Module, RequestMethod } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { NextModule } from './next/next.module';
import { NextMiddleware } from './next/next.middleware';
@Module({
imports: [NextModule], // 这里添加NextModule
controllers: [AppController],
providers: [AppService],
})
export class AppModule {
public configure(consumer: MiddlewareConsumer) {
AppModule.handleAssets(consumer);
}
// 注意:这里很重要,_next*是nextjs静态资源请求的前缀,这里这么处理是将静态资源相关的请求由Nest转交个Next处理
private static handleAssets(consumer: MiddlewareConsumer):void {
consumer.apply(NextMiddleware)
.forRoutes({
path: '_next*',
method: RequestMethod.GET
})
}
}
修改入口文件main.ts
/* main.ts */
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { NextModule } from './next/next.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 使next初始化
await app.get(NextModule).prepare().then(()=>{
app.listen(3000);
})
}
bootstrap();
修改controller,上面的配置只将静态资源转交给了Next处理,页面请求还没有转交,所以我们在controller中将所需的页面进行转交
/* app.controller.ts */
import { Controller, Get, Req, Res } from '@nestjs/common';
import { NextService } from './next/next.service';
import {Request, Response} from 'express';
@Controller()
export class AppController {
constructor(
private readonly next: NextService
) {}
@Get()
getHello(@Req() req:Request, @Res() res: Response) {
// 把原本由Nest处理的主页转交给next
return this.next.render("/index", req, res);
}
}
至此,代码层面的修改已经完成了,然后修改一下配置文件
/* tsconfig.json */
{
"compilerOptions": {
"jsx": "preserve",
"target": "ESNext",
"module": "ESNext",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"moduleResolution": "Node",
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true,
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"isolatedModules": true
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx"
],
"exclude": [
"node_modules"
]
}
新增一个tsconfig.server.json
/* tsconfig.server.json */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "CommonJS",
"noEmit": false
},
"include": [
"src"
]
}
在package.json的script添加一条启动命令(默认使用nest启动无法编译react,所以直接使用node启动)
/* package.json */
{
...
"scripts": {
"dev": "cross-env tsnd --project tsconfig.server.json --ignore-watch .next --cls src/main.ts",
...
}
}
然后我们来启动项目
yarn dev
等项目启动完成,再回到浏览器中刷新下页面,看到如下界面,说明NestJS与Next.js集成完成了
文章转载或复制请以超链接形式并注明出处陌上小筑
发表评论