🐞

サブディレクトリのNext.jsをVS Codeでデバッグする

2023/05/13に公開

はじめに

最近Next.jsを使ってあれこれする機会がありました。そのため、公式ドキュメントを見る機会が多々あり、その中でデバッグに関する項目があったのでそれをやってみた記録になります。

https://nextjs.org/docs/pages/building-your-application/configuring/debugging

結論

急いでいる人向けのまとめです。これで理解できる方はこの記事を読む必要はありません。

環境構築

詳細は長くなるので割愛しますが、devcontainerを使った環境を使用します。該当のコミットは以下のリンクから確認できます。

https://github.com/tunamaguro/nextjs-vscode-debugger-sample/commit/63eb07d01722cc5113e6d799b9602c317a067321

各種バージョンは以下のようになっています。

node -v
v18.16.0

npm -v
9.5.1

続いて、create-next-appNext.jsアプリのひな形を作成します。
今回はtypescript+App Routerを選び、その他の項目は適当に設定しています。

npx create-next-app@latest

✔ What is your project named? … app
✔ Would you like to use TypeScript with this project? …  Yes
✔ Would you like to use ESLint with this project? … Yes
✔ Would you like to use Tailwind CSS with this project? … No
✔ Would you like to use `src/` directory with this project? … Yes
✔ Use App Router (recommended)? … Yes
✔ Would you like to customize the default import alias? … No
Creating a new Next.js app in /workspaces/app.

一応動作確認をしておきます。

cd app && npm run dev

問題なさそうですね!

公式ドキュメント通りにやってみる

何はともあれ公式ドキュメントの通りにやってみます。

https://nextjs.org/docs/pages/building-your-application/configuring/debugging

.vscode/launch.jsonに設定をすれば良いようなのでコピペします。

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Next.js: debug server-side",
      "type": "node-terminal",
      "request": "launch",
      "command": "npm run dev"
    },
    {
      "name": "Next.js: debug client-side",
      "type": "chrome",
      "request": "launch",
      "url": "http://localhost:3000"
    },
    {
      "name": "Next.js: debug full stack",
      "type": "node-terminal",
      "request": "launch",
      "command": "npm run dev",
      "serverReadyAction": {
        "pattern": "started server on .+, url: (https?://.+)",
        "uriFormat": "%s",
        "action": "debugWithChrome"
      }
    }
  ]
}

PythonでデバッグするようにCtrl + Shift + Dでデバッグパネルを開いてF5でデバッガーを起動します。今回はNext.js: debug server-sideを選んで起動させます。


ダメみたいですね...

設定を修正する

できないだけで終わるのは悔しいのでもう少し試してみます。
コンソールの出力を見る限り出力を見る限り、コマンドの実行位置がおかしいのが原因に思えます。
なので、いったん.vscode/launch.json/app下に移動させそれで正しく動作するか確認します。

正しく起動しているようです。ここでは割愛しますが、サーバ側、クライアント側ともにブレークポイントが正常に起動していることを確認できました。

ですがこのままだとデバッグをするたびに/appに移動する必要があるため非常に手間です。
「何とかできないかな~」とネットの海を彷徨っていたところ、VS Codeworkspaceという機能を発見しました。
この機能を利用することで特定のディレクトリでのみ有効なデバッガーを利用することができるようなので早速使ってみます。

Persist task and debugger launch configurations that are only valid in the context of that workspace.
そのワークスペースのコンテキストでのみ有効なタスクとデバッガーの起動設定を永続化します。(Deepl翻訳)

https://code.visualstudio.com/docs/editor/workspaces
https://code.visualstudio.com/docs/editor/multi-root-workspaces#_workspace-launch-configurations

詳しい説明は上記2つのリンクに書かれているので詳しい情報が知りたい方はそちらをご覧ください。
今回はworkspaces.code-workspaceという名前で設定ファイルを作成し、以下のように記載しました。

{
 "folders": [
  {
   "name": "workspaces",
   "path": "."
  },
  {
   "name": "frontend",
   "path": "./app"
  }
 ],
 "settings": {},
 "extensions": {},
}

上のようなファイルを作成しFile -> Open Workspace from File...から先ほど作成したファイルを選択します。すると

デバッグパネルから/app下のlaunch.jsonを起動できます!!

この状態のコミットはこちらです。

Route Handlersのデバッグ

はじめにRoute Handlers(pagesで言うAPI Routes)の動作チェックをしてみます。

https://nextjs.org/docs/app/building-your-application/routing/router-handlers

上記を参考にapp/api/route.tsを以下の内容で作成しました。

import { NextResponse } from 'next/server';

export async function GET(request: Request) {
    console.log("Hello Server!")
    return NextResponse.json({ greet: "Hello!" })
}

http://localhost:3000/apiにブラウザでアクセスすると予想通り{ greet: "Hello!" }と表示され、同時にサーバ側ではHello Server!が出力されています。

---略---
Waiting for the debugger to disconnect...
Hello Server!
Waiting for the debugger to disconnect...

ここで4行目のconsole.log("Hello Server!")にブレークポイントを設定し、止まればいい感じですが...

しっかり止めてくれました!!

Server Componentsのデバッグ

続いてServerComponentsがデバッグできるかどうか確認します。

/src/app/page.tsxを以下のように書き換えます。

// import(略)
async function getCreatedAt() {
  const createdAt = new Date()
  return new Promise<Date>((resolve) => {
    setTimeout(() => {
      resolve(createdAt)
    }, 2000)
  })
}

export default async function Home() {
  const createdAt = await getCreatedAt()

  return (
    <main className={styles.main}>
      <div>{createdAt.toISOString()}</div>
// 以下略

2秒待つ関数getCreatedAtを作成しそれを埋め込んでいます。デフォルトがServerComponentsなのでgetCreatedAtはサーバー側で実行されるため、先ほどと同じように止まるはずです。

予想通りしっかり停止し、返されている値も問題なさそうです。

現在の状態のコミットはこちらです。

Client Componentsのデバッグ

最後にClient Componentsもデバッグができるか確かめます。

src/app/ClientCountUp.tsxを以下の内容で作成し、page.tsxに配置します。

  • src/app/ClientCountUp.tsx
'use client';

import { useState } from 'react';

export function ClientCountUp() {
  const [count, setCount] = useState(0);
  function countUp() {
    console.log("count Up!")
    setCount(count + 1);
  }

  return (
    <button onClick={countUp}>{count}</button>
  );
}
  • src/app/page.tsx
// import(略)
import { ClientCountUp } from './ClientCountUp'

// 略

export default async function Home() {
  const createdAt = await getCreatedAt()

  return (
    <main className={styles.main}>
      <ClientCountUp />
// 略

こちらは単純にクリックした回数を数えるだけのコンポーネントですが、Server ComponentsuseStateのようなフックを利用できないので"use client"宣言をしています。
そのため、これはクライアント側でレンダリングされます。

クライアント側も問題なくデバッグできています。
現在の状態のコミットはこちらです。

おわりに

VS Codeworkspaceを利用することで、開いているディレクトリがNext.jsのルートディレクトリでなくともデバッガーを使用することができました。これでもうconsole.logとおさらばできます。
今回workspaceという機能ををはじめて知ったのですが、ドキュメントを読む限りほかにもいろいろなことができそうです。また誰かの役に立ちそうなことがあれば、記事に書きたいと思います。

ここまで駄文をお読みくださりありがとうございました。

Discussion