Next.js + NestJS で完全TypeScriptでSSR してHerokuにデプロイする方法を誰よりもわかりやすく説明します (モノレポ化)

# はじめに

どうも[yoshii](https://twitter.com/ganja_tuber)です。
今まで、いろんなwebアプリ作ってきたんですが、どれもフロントエンドとバックエンドを別サーバーに置いてSPAで実装するばかりで、たまにはSPA以外もやってみたいなと思ったのが発端でした。

ちなみに今まで作ったアプリとかは大体こちらにまとめてます。基本的になんのありがたみもないようなものを作っています。
暇人は見てね。

https://yoshii.pro/products/

やることはタイトルの通りです。
1つのプロジェクト内でNext.jsとNestJSを配置し、
フロントエンドとバックエンドをそれぞれ担当させ、1つのサーバーに配置し、SSR(サーバーサイドレンダリング)まで実装します。
正直、今までwebアプリ作ったことないような人でも、愚直にこの記事通りやればwebアプリの雛形が作れると思いますので、そういう人も是非是非読んでみてください。
質問も答えられる範囲であれば、答えます！

イメージとしては、`yarn start`でNestJSサーバーが立ち上がり、サーバーにアクセスしたらNext.jsで実装したフロントエンドがレンダリングされるって感じです。

ちなみに、Next.js公式はSSRより圧倒的にSSGを推奨していますが、サーバーで複雑な動きさせたいってなるとNestJSのプロジェクトにNextを配置するような形が良いとも思います。
なので、とにかく色々作りたいタイプには、まずは汎用性の高いSSRからやるのをおすすめします。

SSRはパフォーマンスがSSGよりも落ちる代わりに、SSRさえできればどんな形のアプリにでもオールマイティに対応できると思います。
またSSRはSEO面も良い(検索した時に上の方に出てきやすい)です。

それに、このNext.jsでSSRを実装したらちょっとビルド方法変えて関数名変えたらSSGもできるので、SSGしたい人も参考になるかもしれません。

# 目次

- [前提知識](#前提知識)
- [構成とか](#構成とか)
- [GitHub](#GitHub)
- [参考にしたもの](#参考にしたもの)
- [実装](#-実装)
    - [プロジェクトの作成](#プロジェクトの作成)
    - [各種ライブラリの導入](#各種ライブラリの導入)
    - [Next.jsの実装](#Next.jsの実装)
    - [サーバーサイドの編集](#サーバーサイドの編集)
    - [サーバーサイドからデータを受け取って表示してみる](#サーバーサイドからデータを受け取って表示してみる)
    - [動的ルーティングを実装](#動的ルーティングを実装)
- [デプロイ](#デプロイ)
- [まとめ](#まとめ)



# 前提知識

Next.jsとかNestJSとかSSRとか、そもそもなんだそれはって人向けに筆者の曖昧な知識で解説します。

```
- React: webアプリの見えてる部分(フロントエンド)の開発をスマートにできる部品の集まり。(ライブラリ)
- Next.js: Reactをさらにスマートにして、できることも増やしたやつ。React用フレームワーク。
- NestJS: webアプリの見えない部分(バックエンド)の開発をスマートにできる雛形。(フレームワーク)
- SSR: 各ページにアクセス(遷移)するたびにサーバー側にリクエストが飛ぶような方式。よくわかんない人はスルーしていいです。
- TypeScript: 言語。JavaScriptを静的型付け言語に拡張したもの。安全に開発しやすい。
- heroku: 無料枠が優秀なPaaS。NestJSを簡単に動かせるサーバー。ここにデプロイ(公開)するので、アカウントを作っておいてください。
- モノレポ: フロントエンドとバックエンドを1個のリポジトリにまとめて、1つのコマンドで起動するようなプロジェクト(だと思っています)。
- yarn: nodejsのパッケージマネージャー。jsで作られたライブラリ(機能の部品)を管理するやつ。
    - npmでもいいですが、個人的にyarnが好きなのでよくわかんない人もとりあえず入れてください。
    - Homebrewってやつでnodenvとかanyenvとかを入れて、npmからyarnを入れるのが普通かな…？
```

- SSRとかSPAとかの参考記事: https://shimablogs.com/spa-ssr-ssg-difference
- node.jsのインストール参考記事: https://qiita.com/kyosuke5_20/items/eece817eb283fc9d214f

こんなもんでしょうか。わかんない言葉あったら質問ください。多分俺もわかんないですが、一緒に考えましょう。質問がくるたびにここが増えていくような気がします。

# 構成とか

PC: macOS Big Sur
nodeのversion: v14.17.3
パッケージマネージャー: yarn

バックエンド: NestJS
フロントエンド: Next.js
言語: TypeScript
サーバー: Heroku

# GitHub

手順とかどうでもいいからテンプレ見せろって感じなら、俺の公開してるリポジトリ見てください。

https://github.com/yoshiishunichi/nest-next-template

# 参考にしたもの

正直、実装に関しては以下の記事ほぼそのままです。
deepLさんで和訳しながら実装しました。

https://dev.to/yakovlev_alexey/creating-a-project-with-nestjs-nextjs-3i1i

# 実装

では実装に入っていきます！

## プロジェクトの作成

まずはプロジェクトを作ります。

nest cliを入れておきましょう。
NestJS公式を見て入れようね。

https://docs.nestjs.com/

ターミナルで以下を実行

```
nest new nest-next-template
```

この時の`nest-next-template`ってとこは好きなプロジェクトの名前にしてね。
npmにするかyarnにするか聞かれるので好きな方選んでね。(筆者はyarn派です)

今回、テストはちょっと考えないことにしたいので、nest cliによって勝手に作られる`src/test`ディレクトリと、`src/app.controller.spec.ts` は削除しておきましょう。

そして、NestJSプロジェクトが作成されたら、`src`ディレクトリの中に`pages`, `server`, `shared` ディレクトリを作りましょう。

現状`src`ディレクトリの中身はこんな感じ

```
src/
├── app.controller.ts
├── app.module.ts
├── app.service.ts
├── main.ts
├── pages/
├── server/
└── shared/
```

### 〜 各ディレクトリの解説 〜

```
- pages/: Next.jsのpagesに相当するファイル群をここに置いていく
- server/: NestJSのファイル群をここに置いていく
- shared/: Next.jsとNestJSで共通の情報をここに配置する。型定義ファイルとかが多い。
```
なので、まずは`src/server`ディレクトリにnest cliで作成されたファイルたちを移動しましょう。

移動した後はこんな感じ

```
src/
├── pages
├── server
│   ├── app.controller.ts
│   ├── app.module.ts
│   ├── app.service.ts
│   └── main.ts
└── shared
```

次に、`nest-cli.json` ファイルを以下のように編集。

```json:nest-cli.json
{
  "collection": "@nestjs/schematics",
  "sourceRoot": "src",
  "entryFile": "main"
}
```

これでプロジェクトの初期化はとりあえず終わり。

## 各種ライブラリの導入

Next.js関連のライブラリを入れます。
ターミナルで以下を入力してください。

```
yarn add next react react-dom
```
```
yarn add -D @types/react @types/react-dom eslint-config-next
```

上記に加えて先で必要になるライブラリも今のうちに入れておきましょう
ターミナルで以下を入力。

```
yarn add nest-next axios
```

現状必要なライブラリはこんなものですかね。
Reactはフレームワークではなくライブラリなので、こんな感じで、最小構成のフロントエンド開発環境をNestJSプロジェクトに簡単に追加できたりします。(どこかで読んだ話ですが…)

ここから本格的に実装に入っていきましょう。

## Next.jsの実装

フロントエンドの実装に入る前に、tsconfigファイルを用意してあげましょう。
ルートディレクトリに`tsconfig.server.json`を作成します。

```json:tsconfig.server.json
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "noEmit": false
  },
  "include": [
    "./src/server/**/*.ts",
    "./src/shared/**/*.ts",
    "./@types/**/*.d.ts"
  ]
}
```

また、ルートディレクトリに存在している`tsconfig.json`を、以下のように編集します。

```json:tsconfig.json
{
  "compilerOptions": {
    "module": "commonjs",
    "declaration": true,
    "removeComments": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "target": "es2017",
    "sourceMap": true,
    "outDir": "./dist",
    "baseUrl": "./",
    "incremental": true,
    "skipLibCheck": true,
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "esModuleInterop": true,
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve"
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
  "exclude": ["node_modules"]
}
```

その後、`package.json`のscriptsの部分を以下のように書き換えます。これで`yarn build`でNext.jsとNestJS両方のビルドが行われたり、とにかく上手いことやってくれるようになります。

```json:package.json
  "scripts": {
    "prebuild": "rimraf dist",
    "build": "yarn build:next && yarn build:nest",
    "build:next": "next build",
    "build:nest": "nest build --path ./tsconfig.server.json",
    "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
    "start": "node dist/server/main.js",
    "start:prod": "node dist/server/main.js",
    "start:next": "next dev",
    "start:dev": "nest start --path ./tsconfig.server.json --watch",
    "lint": "eslint \"{src,apps,libs,test}/**/*.tsx?\" --fix"
  },
```

よくわからん人は、とりあえず`package.json`に以下の内容をコピペしたってください。

```json:package.json
{
  "name": "nest-next-template",
  "version": "0.0.1",
  "description": "",
  "author": "",
  "private": true,
  "license": "UNLICENSED",
  "scripts": {
    "prebuild": "rimraf dist",
    "build": "yarn build:next && yarn build:nest",
    "build:next": "next build",
    "build:nest": "nest build --path ./tsconfig.server.json",
    "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
    "start": "node dist/server/main.js",
    "start:prod": "node dist/server/main.js",
    "start:next": "next dev",
    "start:dev": "nest start --path ./tsconfig.server.json --watch",
    "lint": "eslint \"{src,apps,libs,test}/**/*.tsx?\" --fix"
  },
  "dependencies": {
    "@nestjs/common": "^8.0.0",
    "@nestjs/core": "^8.0.0",
    "@nestjs/platform-express": "^8.0.0",
    "axios": "^0.26.0",
    "nest-next": "^9.6.0",
    "next": "^12.0.10",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "reflect-metadata": "^0.1.13",
    "rimraf": "^3.0.2",
    "rxjs": "^7.2.0"
  },
  "devDependencies": {
    "@nestjs/cli": "^8.0.0",
    "@nestjs/schematics": "^8.0.0",
    "@nestjs/testing": "^8.0.0",
    "@types/express": "^4.17.13",
    "@types/jest": "^27.0.1",
    "@types/node": "^16.0.0",
    "@types/react": "^17.0.39",
    "@types/react-dom": "^17.0.11",
    "@types/supertest": "^2.0.11",
    "@typescript-eslint/eslint-plugin": "^4.28.2",
    "@typescript-eslint/parser": "^4.28.2",
    "eslint": "^7.30.0",
    "eslint-config-next": "^12.0.10",
    "eslint-config-prettier": "^8.3.0",
    "eslint-plugin-prettier": "^3.4.0",
    "jest": "^27.0.6",
    "prettier": "^2.3.2",
    "supertest": "^6.1.3",
    "ts-jest": "^27.0.3",
    "ts-loader": "^9.2.3",
    "ts-node": "^10.0.0",
    "tsconfig-paths": "^3.10.1",
    "typescript": "^4.3.5"
  },
  "jest": {
    "moduleFileExtensions": [
      "js",
      "json",
      "ts"
    ],
    "rootDir": "src",
    "testRegex": ".*\\.spec\\.ts$",
    "transform": {
      "^.+\\.(t|j)s$": "ts-jest"
    },
    "collectCoverageFrom": [
      "**/*.(t|j)s"
    ],
    "coverageDirectory": "../coverage",
    "testEnvironment": "node"
  }
}
```

`package.json`を編集した後は、一応ターミナルで`yarn install`しておきましょうか。
豆知識ですが、`yarn install`は`yarn`と入力するだけで実行されます。どうでもいいですね。

次に、`src/pages/`以下に`_app.tsx`と`index.tsx`、2つのファイルを追加

```tsx:src/pages/_app.tsx
import type { AppProps } from 'next/app';

function MyApp({ Component, pageProps }: AppProps) {
  return <Component {...pageProps} />;
}

export default MyApp;
```
```tsx:src/pages/index.tsx
import type { NextPage } from 'next';

const Home: NextPage = () => {
  return <h1>Home</h1>;
};

export default Home;
```

この時点で、ターミナルに`yarn start:next`と入力して http://localhost:3000/ にアクセスしてやると、Homeって出てくると思います。

あと、`.gitignore`にNext.jsのビルドディレクトリを追加しましょう。

```markdown:.gitignore
# compiled output
/dist
/node_modules
/.next

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*

# OS
.DS_Store

# Tests
/coverage
/.nyc_output

# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace

# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
```

## サーバーサイドの編集

ここからサーバーサイド(NestJS)のファイルを編集していきます。

まずは、`main.ts`。デプロイした時にポート番号を入力しなくて良くなるようにします。

```ts:src/server/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(process.env.PORT || 3000);
}
bootstrap(); 
```

次に、`app.module.ts` を編集。`nest-next`ライブラリを使って、Next.jsをレンダリングできるようにします。`dev: true`を指定すると、ファイルの更新を監視して変更してくれます。(ホットリロード的な？)

```ts:src/server/app.module.ts
import { Module } from '@nestjs/common';
import { RenderModule } from 'nest-next';
import Next from 'next';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [RenderModule.forRootAsync(Next({ dev: true }), { viewsDir: null })],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
```

最後に、`app.controller.ts` を編集。`@Render`デコレータを使って、`index.tsx`をレンダリングします。

```ts:src/server/app.controller.ts
import { Controller, Get, Render } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  @Render('index')
  home() {
    return {};
  }
}
```

ここまでできたら、一度`yarn start:dev`とターミナルに入力してみてください。
ルートディレクトリにNext.jsとNestJSのビルドディレクトリがそれぞれ生成され、NestJSのサーバーが起動します。
http://localhost:3000/ にアクセスすると、`index.tsx`がレンダリングされ、Homeって表示されると思います。

とりあえずここまででcommitを載せておきます。

https://github.com/yoshiishunichi/nest-next-template/tree/522e8777843c01283c2cdc38b6a67036d114b71a

## サーバーサイドからデータを受け取って表示してみる

まず、型定義ファイル`src/shared/types/index.d.ts`を作成し、Story型を作ります。

```ts:src/shared/types/index.d.ts
export type Story = {
  id: number;
  title: string;
  description: string;
};
```

とりあえず、データベース用意するの面倒なので、データは`app.service.ts`内に定数として直接持たせちゃいましょう。
本来はデータベースからfetchしてくると思ってください。

```ts:src/server/app.service.ts
import { Injectable } from '@nestjs/common';
import { Story } from 'src/shared/types';

const stories: Story[] = [
  {
    id: 1,
    title: '国歌',
    description: '首相が豚と…',
  },
  {
    id: 2,
    title: '1500万メリット',
    description: '超管理社会',
  },
  {
    id: 3,
    title: '人生の軌跡のすべて',
    description: '記憶をデータとして正確に閲覧できる世界の話',
  },
];

@Injectable()
export class AppService {
  getStories() {
    return stories;
  }
}
```

最近ネトフリで見て面白かった「[ブラック・ミラー](https://www.netflix.com/title/70264888)」というドラマシリーズのタイトルとあらすじを3つ用意してみました。(関係ないですがとてもおすすめのドラマです)

次に、`app.controller.ts`でサービスを呼び出して、Storyの配列を返すAPIを定義しましょう。

```ts:src/server/app.controller.ts
import { Controller, Get, Render } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  @Render('index')
  home() {
    return {};
  }

  @Get('/api/stories')
  public getStories() {
    return this.appService.getStories();
  }
}
```

では、Story配列をフロントエンドで受け取って表示してみましょう。

まず、`axios`を使ってフロントエンドとバックエンドの通信を行うのですが、ベースURLを省略したいのと、`application/json`形式で通信したいので、それをラップしてexportするファイルを用意します。

`src/shared/lib/apiClient.ts`ファイルを以下のように作成します。

```ts:src/shared/lib/apiClient.ts
import axios from 'axios';

export const apiClient = axios.create({
  baseURL: 'http://localhost:3000/',
  responseType: 'json',
  headers: {
    'Content-Type': 'application/json',
  },
});
```

そして、`src/pages/index.tsx` に`getServerSideProps` 関数を用意して、サーバーサイドでAPIを叩きましょう。

```tsx:src/pages/index.tsx
import type { GetServerSideProps, NextPage } from 'next';
import { apiClient } from 'src/shared/lib/apiClient';
import { Story } from 'src/shared/types';

type HomeProps = {
  stories: Story[];
};

export const getServerSideProps: GetServerSideProps<HomeProps> = async () => {
  const response = await apiClient.get<Story[]>('/api/stories');
  return { props: { stories: response.data } };
};

const Home: NextPage<HomeProps> = (props) => {
  const { stories } = props;

  return (
    <div>
      <h1>Home</h1>
      <ul>
        {stories.map((story) => (
          <li key={story.id}>
            {story.id}: {story.title}
          </li>
        ))}
      </ul>
    </div>
  );
};

export default Home;
```

さらに、`src/server`ディレクトリ以外のディレクトリのファイルもNestJSのビルドファイルに含まれることになるので、`nest-cli.json`を以下のように編集しましょう。

```json:nest-cli.json
{
  "collection": "@nestjs/schematics",
  "sourceRoot": "src",
  "entryFile": "server/main"
}
```

これで、`yarn start:dev`を実行して、 http://localhost:3000/ にアクセスすると、こんな感じの画面が見えるはず

![スクリーンショット 2022-02-14 16.33.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2088889/8b5239a2-e477-59a1-586a-454d38765065.png)

これらの情報はすべてサーバーサイドで取得しているので、動的ogpなんかにも使えます。

もし、うまく行かなかった人は、一回`yarn build`してから、再度`yarn start:dev`を実行してみてください。
NestJSのビルドディレクトリ`dist`の中を見て、main.jsがserverディレクトリ内にあるか確認してください。

ここまででcommitしておきます。

https://github.com/yoshiishunichi/nest-next-template/tree/a1f5ad32bc7282fd12c4e5900c62e34292f0ae06

## 動的ルーティングを実装

まずは`app.service.ts`に、idを受け取ってstoryを返却する関数を定義します。

```ts:src/server/app.service.ts
import { Injectable } from '@nestjs/common';
import { Story } from 'src/shared/types';

const stories: Story[] = [
  {
    id: 1,
    title: '国歌',
    description: '首相が豚と…',
  },
  {
    id: 2,
    title: '1500万メリット',
    description: '超管理社会',
  },
  {
    id: 3,
    title: '人生の軌跡のすべて',
    description: '記憶をデータとして正確に閲覧できる世界の話',
  },
];

@Injectable()
export class AppService {
  getStories() {
    return stories;
  }

  getStory(id: number) {
    return stories.find((story) => story.id === id) ?? null;
  }
}
```

次に、インターセプターというやつを実装します。`rxjs`使ってややこしい感じですが、簡単に言うとこれでNestJSのcontrollerからNext.jsの`getServerSideProps`にパスパラメーターを渡してるってことだと思います。

インターセプターのドキュメント(日本語訳): https://zenn.dev/kisihara_c/books/nest-officialdoc-jp/viewer/overview-interceptors

`src/server/params.interceptor.ts`を以下のように作成します。

```ts:src/server/params.interceptor.ts
import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
} from '@nestjs/common';
import { Request } from 'express';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

@Injectable()
export class ParamsInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
    const request = context.switchToHttp().getRequest() as Request;

    return next.handle().pipe(
      map((data) => {
        return {
          ...request.query,
          ...request.params,
          ...data,
        };
      }),
    );
  }
}
```

そして、`app.controller.ts`のNext.jsをレンダリングする関数に、`UseInterceptors`デコレーターをつけてあげます。

```ts:src/server/app.controller.ts
import {
  Controller,
  Get,
  Param,
  Render,
  UseInterceptors,
} from '@nestjs/common';
import { AppService } from './app.service';
import { ParamsInterceptor } from './params.interceptor';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  @Render('index')
  @UseInterceptors(ParamsInterceptor)
  home() {
    return {};
  }

  @Get(':id')
  @Render('[id]')
  @UseInterceptors(ParamsInterceptor)
  public storyPage() {
    return {};
  }

  @Get('/api/stories')
  public getStories() {
    return this.appService.getStories();
  }

  @Get('/api/story/:id')
  public getStory(@Param('id') id: number) {
    return this.appService.getStory(Number(id));
  }
}
```

`storyPage()`関数で、 http://localhost:3000/:id にアクセスされた時に`src/pages/[id]/index.tsx`をレンダリングします。
`getStory`関数で、パスパラメータからidを受け取ってストーリーを返します。

仕上げにフロントエンド側を作成しましょう。
`src/pages/[id]/index.tsx`を以下のように作成します。

```tsx:src/pages/[id]/index.tsx
import { GetServerSideProps, GetServerSidePropsContext, NextPage } from 'next';
import Link from 'next/link';
import { apiClient } from 'src/shared/lib/apiClient';
import { Story } from 'src/shared/types';

type StoryPageProps = {
  story: Story;
};

export const getServerSideProps: GetServerSideProps<StoryPageProps> = async (
  ctx: GetServerSidePropsContext,
) => {
  const id = ctx.query.id;
  if (id) {
    const response = await apiClient.get<Story | null>(`/api/story/${id}`);
    return response.data
      ? {
          props: { story: response.data },
        }
      : { notFound: true };
  } else {
    return {
      notFound: true,
    };
  }
};

const StoryPage: NextPage<StoryPageProps> = (props) => {
  const { story } = props;

  return (
    <div>
      <Link href={'/'}>
        <a>Home</a>
      </Link>
      <h1>story: {story.title}</h1>
      <p>あらすじ: {story.description}</p>
    </div>
  );
};

export default StoryPage;
```

最後に、`src/pages/index.tsx`からのリンクも用意しましょう。

```tsx:src/pages/index.tsx
import type { GetServerSideProps, NextPage } from 'next';
import Link from 'next/link';
import { apiClient } from 'src/shared/lib/apiClient';
import { Story } from 'src/shared/types';

type HomeProps = {
  stories: Story[];
};

export const getServerSideProps: GetServerSideProps<HomeProps> = async () => {
  const response = await apiClient.get<Story[]>('/api/stories');
  return { props: { stories: response.data } };
};

const Home: NextPage<HomeProps> = (props) => {
  const { stories } = props;

  return (
    <div>
      <h1>Home</h1>
      <ul>
        {stories.map((story) => (
          <li key={story.id}>
            <Link href={`/${story.id}`}>
              <a>
                {story.id}: {story.title}
              </a>
            </Link>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default Home;
```

これで、`yarn start:dev`して、http://localhost:3000/ にアクセスすると、リンクが表示されます。(ダメだったら先に`yarn build`してね)

![スクリーンショット 2022-02-14 17.50.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2088889/72305228-63ba-fff3-18cc-f47154a45988.png)

また、リンクをクリックしたり、http://localhost:3000/1 にアクセスすると、APIを叩いて以下のようにタイトルとあらすじが表示されます。

![スクリーンショット 2022-02-14 21.55.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2088889/5f4e658d-2420-6cb8-4ee5-c9145bb5535a.png)

これで実装完了です。
あとはSSRでサーバーから好きにデータ受け取っちゃいましょう！

ここまでのcommitはこちら

https://github.com/yoshiishunichi/nest-next-template/tree/50a49be7bb5ac2cc7d907fa78d39fce83fccac77

# デプロイ

では、いよいよデプロイしていきましょう

herokuのアカウントを作ってログインしておいてください。
メアドがあれば無料でアカウント作成できるし、クレカ情報を登録すれば無料でLinux コンテナを990時間とか起動できるので、完全無料で1個のサービスを常駐させれます。

https://dashboard.heroku.com/

ログインしたら、create new appを押すか、以下にアクセスしてください。

https://dashboard.heroku.com/new-app

App nameに好きな名前を入力してください。筆者は`nest-next-template-in-heroku`にしました。

![スクリーンショット 2022-02-14 18.12.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2088889/ee58366b-04c3-fd3d-6b44-9f8ce6049e7c.png)

入力できたら、Create appボタンを押してください。

これでデプロイ先のLinux コンテナが起動しました。

では、ここにGitHub Actionsでデプロイします。

`.github/workflows/deploy.yml`を以下のように作成します。

```yml:.github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches:
      - master

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: akhileshns/heroku-deploy@v3.12.12
        with:
          heroku_api_key: ${{secrets.HEROKU_API_KEY}}
          heroku_app_name: ${{secrets.HEROKU_APP}}
          heroku_email: ${{secrets.HEROKU_EMAIL}}
```

これで、masterブランチにpushするとデプロイが走りますが、pushするのはちょっと待ってくださいね。

pushする先のGitHubリポジトリの設定を変更します。(まだ作成していない人は作成しておいてください)

Settings -> Secrets -> Actions から 「New repository secret」ボタンを押してください。
![スクリーンショット 2022-02-14 18.23.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2088889/3e1a8811-c68f-1011-3975-a9f71f36912d.png)

Nameに`HEROKU_API_KEY`、ValueにherokuのAPIキーを入力して「Add secret」ボタンを押して保存してください。

herokuのAPIキーは[Account Settings](https://dashboard.heroku.com/account)から、API Key
のところの「Reveal」ボタンを押して表示されたAPIキーをコピペしてあげてください。

![スクリーンショット 2022-02-14 18.27.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2088889/727ed727-dcf9-1263-ca6f-6d31419c21b2.png)

同様に、「New repository secret」ボタンから、`HEROKU_APP`, `HEROKU_EMAIL`を設定しましょう。

- HEROKU_APP: herokuのapp name(筆者の場合は`nest-next-template-in-heroku`)
- HEROKU_EMAIL: herokuに登録したメールアドレス

次に、baseURLを環境によって切り替えます。
herokuでApp name一覧からApp nameを選択してSettingsからConfig Varsに、KEY: NODE_ENV, VALUE: productionを設定して、Addボタンを押してください。
これで、デプロイされた時に環境変数`process.env.NODE_ENV`で参照することができます。

![スクリーンショット 2022-02-14 18.36.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2088889/4c58bab1-f668-9a4a-3f3b-67e754260f59.png)

これに伴い、`shared/lib/apiClient.ts`を以下のように編集します。
DEPLOY_URL ってところは、デプロイ先のURLを入力してください。本来は`.env`とかで定義すべきですが、テンプレートでやるの面倒なので今回はやりません。
ちなみに公開してるリポジトリでは最終的に環境変数も導入してるので、よかったら見てね。

```ts:shared/lib/apiClient.ts
import axios from 'axios';

const nodeEnv = process.env.NODE_ENV ?? 'development';

export const apiClient = axios.create({
  baseURL:
    nodeEnv === 'production'
      ? DEPLOY_URL
      : 'http://localhost:3000/',
  responseType: 'json',
  headers: {
    'Content-Type': 'application/json',
  },
});
```

また、`src/server/app.module.ts`のdevオプションを変更します。

```ts:src/server/app.module.ts
import { Module } from '@nestjs/common';
import { RenderModule } from 'nest-next';
import Next from 'next';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [
    RenderModule.forRootAsync(Next({ dev: false }), { viewsDir: null }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
```
これによりNext.jsのdevモードが解除されます。
このオプションに関しても、本来なら環境変数を参照して、開発環境とでデプロイ先で切り替えるような実装が望ましいと思います。

最後にルート直下に`Procfile`を追加しましょう。これで起動コマンドを指定できます。
ビルドはデプロイ後に自動で走るので、起動コマンドの`yarn start:prod`を指定してあげます。

```markdown:Procfile
web: yarn start:prod
```

これらを設定できたら、コミットしてmasterブランチにプッシュしましょう。

すると、GitHub Actionsが走って…

デプロイ先のURL(筆者の場合は https://nest-next-template-in-heroku.herokuapp.com/ )にアクセスすると、実装したHome画面が表示されると思います。

デプロイのcommitはこちら

https://github.com/yoshiishunichi/nest-next-template/tree/60547785b45bf49597597e6c99f995fe2ae6154c

# まとめ

お疲れ様でした！
めちゃくちゃ丁寧にNext.js+NestJSのモノレポを作成する手順を解説してみました。
これ読んでwebアプリ作ったことないような人もトライしてくれるとすごい嬉しいです！

あと、[Twitter](https://twitter.com/ganja_tuber)もやってますが、特にありがたみのないツイートしかしてないです…
お酒が好きなので、酒飲みは良かったらフォローして友達になってください