NextJS+NestJSでhello world!
Nest(NestJS)は、効率的且つスケーラブルなNode.jsのサーバーサイドアプリケーションを構築するためのフレームワークです。 JavaScriptの段階的な強化を活用し、純粋なJavaScriptでの開発も可能ですし、TypeScriptでも完全サポート、OOP、FP、FRPも組み合わせて開発することもサポートしています。
Next.jsは、本番環境でも使用できるサーバーサイドレンダリングフレームワークです。SEOに優しいフレームワークではあります。
(ps:この2つのフレームワークは名前が似ているので、間違えないように)。
NestJSはサーバーサイドに特化しており、Next.jsはページレンダリングに特化していますが、この2つのフレームワークが統合されたら完璧だ(実用性を一旦置いとく)と思いますので。この記事では2つのフレームワークを統合して使用する方法を紹介します。
NestJSスタート
まず、Nest-cli を使ってプロジェクトを初期化します。
nest new nest-next
実行結果
NextJSインストール
yarn add next react react-dom
yarn add cross-env ts-node-dev ts-node @types/react --dev
NextJS 周りの設定
1. ↑プロジェクトにpagesフォルダを作ります。作られたフォルダはNextJS用のフォルダだとします。
2. pagesフォルダにindex.tsxを作ります。
/* index.tsx -> Hello Wolrd! */
import React from 'react';
const Index = () => (
<h1>
Hello World!
</h1>
)
export default Index;
3. 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をNestJSにimportするために、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のリソースのprefixなので、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を修正
/* 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) {
// レンダリングをnextに投げる
return this.next.render("/index", req, res);
}
}
いよいよ、tsconfg.json
/* 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で起動コマンドを修正
/* package.json */
{
...
"scripts": {
"dev": "cross-env tsnd --project tsconfig.server.json --ignore-watch .next --cls src/main.ts",
...
}
}
yarn dev
yarn dev
結果
以上、NestJSとNextJSの組み合わせが完成しました。
この記事が気に入ったらサポートをしてみませんか?