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

実行結果

画像1

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

結果

画像2

以上、NestJSとNextJSの組み合わせが完成しました。

この記事が気に入ったらサポートをしてみませんか?