Next.js×TypeScript をカスタムサーバー(Express)で起動する

Next.jsでExpressカスタムサーバーを試してみる機会があり、せっかくなので記事にしました。
`tsconfig` などの具体的な説明は省略しています。

## 環境構築

### Next プロジェクトの作成
ひとまず、Next.js × TypeScript の環境を作成します。 
`--ts` のオプションを付けることでTypeScript化した状態でNext.jsのプロジェクトを生成してくれます。(便利、、、！！)

```bassh
npx create-next-app@latest --ts your-app-name
```

### Expressの導入

必要なパッケージをinstall　
`cross-env` は環境変数設定のためにインストールします。

```bash
npm i express cross-env
npm i --save-dev @types/express 
```

`tsconfig.server.json` を作成します。

```json:tsconfig.server.json
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "module": "CommonJS",
    "outDir": "dist",
    "noEmit": false
  },
  "include": ["server"]
}
```

`server/index.ts` を作成します。

```ts:index.ts
import express, { Request, Response } from "express";
import next from "next";

const dev = process.env.NODE_ENV === "development";
const port = 4000;
const app = next({ dev });
const handle = app.getRequestHandler();

(async () => {
  try {
    await app.prepare();
    const server = express();
    server.all("*", (req: Request, res: Response) => {
      return handle(req, res);
    });
    server.listen(port, () => {
      console.log(`${port}で起動中`);
    });
  } catch (e) {
    console.error(e);
  }
})();
```

cross-envを使用して、環境変数を設定しdevモードかどうかの判定を行います。
`app.prepare()` で別サーバーでNext.jsを使うための初期化を行います。
`app.getRequestHandler()`はハンドラを作成し、expressのserverでリクエストとレスポンスをラップすることで、Nextjs内でURLをルーティングすることが可能になり、通常どおりURLで `/pages` にアクセスすることができます。

続いて、`packege.json` を編集

```json:package.json
{
  "scripts": {
    "dev": "tsc -p tsconfig.server.json && cross-env NODE_ENV=development node ./dist/index.js",
    "build": "tsc -p tsconfig.server.json",
  },
}
```

あとは `npm run dev` or `npm run build`を行い、実行するだけです。
以上の通り、Next.jsはexpressのルーティングをラップしているだけで簡単にカスタムサーバーを構築することができます。


また、expressのエコシステムも問題なく使うことができます。以下のようにserverにミドルウェアを登録してあげることで、`/test` 以下はexpressで作成したAPIを使用することができます。

```ts:router.ts
import express, { Request, Response } from "express";
export const router = express.Router();

router.get("/", (req: Request, res: Response, next) => {
  res.status(200).json(`GET`);
});

router.post("/", (req: Request, res: Response, next) => {
  res.status(200).json(`POST`);
});

```

```ts:index.ts
    const server = express();
    server.use("/test", router);
    server.all("*", (req: Request, res: Response) => {
      return handle(req, res);
    });
```

画面はpages、内部APIはExpressのカスタムサーバーのapiと役割を分けたい時などに良いと思います。

## おまけ: ホットリロードできるようにnodemonで動かす

**[nodemon](https://www.npmjs.com/package/nodemon)** とはNode.jsベースのappのソースコードの変更を監視して、自動でサーバーを再起動してくれるツールです。
特に解説を行いませんが、以下の設定することでソースコードを変更するたびに再起動することがなくなるので、開発効率が上がり非常に便利です。

```jsx
npm i --save-dev nodemon
```

`nodemon.json` を作成します。

```jsx:nodemon.json
{
  "watch": ["./server"],
  "ext": "ts",
  "exec": "ts-node ./server/index.ts"
}
```

`tsconfig.json` と `package.json` を編集します。

```jsonc:tsconfig.json
{
  "compilerOptions": {
　　// 同じ環境で動くようにCommonJSに変更しておく
    "module": "CommonJS",
  }
}

```

```json:package.json
{
  "scripts": {
    "dev": "cross-env NODE_ENV=development nodemon",
  },
}
```

環境変数で`const app = next({ dev })` にdev環境かどうかを教えてあげないとホットリロードされないので注意です。

## 参考資料
・ [https://nextjs-ja-translation-docs.vercel.app/docs/advanced-features/custom-server](https://nextjs-ja-translation-docs.vercel.app/docs/advanced-features/custom-server)
・[https://gonta616.hatenablog.com/entry/2020/05/17/232007](https://gonta616.hatenablog.com/entry/2020/05/17/232007)
・[https://www.forcia.com/blog/001559.html](https://www.forcia.com/blog/001559.html)