ローコードプラットフォーム開発ログ
開発するもの
Next.js製の業務アプリケーションプラットフォーム。
ノーコード/ローコードで業務アプリを構築できるWebサービス。
開発理由
ユーザーが自由にアプリケーションを構築できる基盤を開発してみたい。
既存のローコードアプリケーションよりも柔軟なカスタマイズ機能を持たせたい。
アプリケーション名は後から考える。
Next.jsの導入
公式ドキュメントを見ながらセットアップ。
事前にリポジトリをクローンしてディレクトリ作ってあったので、カレントディレクトリを指定してインストール。
npx create-next-app@latest .
依存関係インストール完了したので起動。
npm run dev
http://localhost:3000/ にアクセスできたのでNext.jsのセットアップ完了!
次はshadcn/uiのセットアップをしていく。
shadcn/ui の導入
こちらも公式ドキュメントを見ながらセットアップ。
npx shadcn@latest init
またもやコマンド一発で完了。
Viteのときはもっと工程多かった気がするけど、Next.jsだと楽すぎる。
base colorはひとまず Neutral を選択。
どうせ使うだろうからbuttonコンポーネントだけインストールしておいた。
npx shadcn@latest add button
堅牢性と品質のためのTSConfig設定
tsconfig.json の compilerOptions に以下の厳格なチェックを設定する。
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"forceConsistentCasingInFileNames": true
}
}
設定の意図(これって何のため?)
| 設定 | 目的と効果 | こんな感じらしい |
|---|---|---|
"strict": true |
型チェックをより厳密にする | TypeScriptの型安全性を最大限に活かすための基本設定。 |
"noImplicitAny": true |
暗黙的な any 型を禁止 |
型を省略した変数に自動でanyがつくのを防ぎ、明示的な型指定を促す。 |
"forceConsistentCasingInFileNames": true |
ファイル名の大文字・小文字の違いを検出 | OS間の大小文字差によるビルドエラーを防ぐ安全策。 |
Prettier の導入と ESLint 連携
Next.jsプロジェクトのコードスタイルを統一するため、ESLint (標準搭載) に加えて Prettier を導入し、最新の ESLint Flat Config 形式で連携させる。
1. Prettier と 競合解消パッケージのインストール
まず、Prettierをインストールする。
npm install -D prettier
ESLint と Prettier の整形ルールが競合しないようにする設定をインストールする。
npm i -D eslint-config-prettier
💡 解説: eslint-config-prettier は、ESLint の整形に関するルール(例: インデント、セミコロンの有無、クォートの種類など)をすべて無効化するための設定です。
これにより、ESLint と Prettier の間でスタイルが衝突するのを防ぎ、Prettier に整形を一任できます。
2. Prettier の設定ファイル作成
プロジェクトルートに .prettierrc.json を作成し、モダンな開発で推奨される設定を適用。
{
"semi": true,
"trailingComma": "all",
"singleQuote": true,
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"bracketSpacing": true,
"arrowParens": "always",
"endOfLine": "lf"
}
3. フォーマットコマンドの設定
既存の package.json の "scripts" セクションに、以下のハイライト部分を追記します。
💡 解説: format コマンドは、スタイルを統一するためのもので、構文エラーや命名規則の不備といったロジックや品質のエラーを検出するのは、引き続き lint コマンド (ESLint) の役割です。
{
// ...
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "eslint",
// ↓コマンドを追加
"format": "prettier --write ."
},
// ...
}
4. Prettier 連携を組み込んだ ESLint 設定
eslint.config.mjs
import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";
import prettier from "eslint-config-prettier/flat";
const eslintConfig = defineConfig([
...nextVitals,
...nextTs,
prettier,
// Override default ignores of eslint-config-next.
globalIgnores([
// Default ignores of eslint-config-next:
".next/**",
"out/**",
"build/**",
"next-env.d.ts",
]),
]);
export default eslintConfig;
5. フォーマットの実行
設定が完了したら、package.json に追加した format コマンドを実行して、プロジェクト全体を一度に整形。
npm run format
Claude CodeのMCP設定
自分の過去記事を見ながら Claude Code に GitHub MCPサーバーを設定。
.mcp.json
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_PERSONAL_ACCESS_TOKEN}",
"GITHUB_OWNER": "ymtdir",
"GITHUB_REPO": "documorph"
}
}
}
}
これでClaude CodeからDocumorphリポジトリのIssueやPRを直接操作できるようになった。
とは言いつつ今回はなるべく手動で書いていく予定。
IssueテンプレートとPRテンプレート作成
自分の過去記事を見ながらGitHubのテンプレートを設定。
作成したファイル
.github/
├── ISSUE_TEMPLATE/
│ ├── bug_template.md
│ └── enhancement_template.md
└── pull_request_template.md
CLAUDE.mdの作成
Claude Codeが最初に読んでくれるファイルで、プロジェクトの全体像・技術スタック・ディレクトリ構成とかをまとめておくと後が楽。
Next.jsのベストプラクティスについては以下の記事を参考にしつつ、
Claudeと壁打ちしながら多少時間をかけて作成。
フォーマット時のルール設定
コードの統一性を保つため、PrettierとESLintを設定。
Prettier
整形ルール
.prettierrc
{
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"printWidth": 80,
"tabWidth": 2,
"arrowParens": "always"
}
各設定:
-
semi: 文末セミコロン -
trailingComma: 末尾カンマ(ES5準拠) -
singleQuote: シングルクォート使用 -
printWidth: 1行80文字まで -
tabWidth: インデント2スペース -
arrowParens: アロー関数の引数に括弧
整形しないファイルの設定
.prettierignore
# Dependencies
node_modules/
# Build outputs
.next/
out/
dist/
build/
# Coverage reports
coverage/
# Static assets
public/
# TypeScript build cache
*.tsbuildinfo
# Log files
*.log
# Lock files
*.lock
package-lock.json
pnpm-lock.yaml
yarn.lock
# Environment files
.env*
.vercel/
# IDE / OS files
.vscode/
.idea/
.DS_Store
Thumbs.db
# Git
.git/
ESLint(コード品質チェック)
.eslintrc.json
{
"extends": ["next/core-web-vitals", "plugin:prettier/recommended"]
}
Next.js推奨ルール + Prettier連携。シンプルにデフォルト設定のまま使用。
Supabaseをセットアップ
公式ドキュメントを見ながらセットアップ。
新たにプロジェクトを作成するのではなく、既存プロジェクトにSupabaseの設定を追加したため多少手順が違かった。
1. プロジェクト内で依存関係をインストール
npm install @supabase/supabase-js @supabase/ssr
2. Supabaseプロジェクトの作成
- 下記URLからSupabaseに会員登録 & ログイン
- プロジェクトを作成し、Project URLとPublishable keyを取得
3. 環境変数の設定
.env
NEXT_PUBLIC_SUPABASE_URL=your-project-url
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=your-anon-key
4. サンプルデータの作成
SQL Editorでサンプルクエリを実行してinstrumentsテーブルとサンプルレコードを作成。
5. 接続確認
-
lib/supabase/server.ts,app/instruments/page.tsxを作成 -
npm run devでアプリケーションを起動 -
http://localhost:3000/instrumentsにアクセスし、データ表示を確認
Prismaの導入
PrismaはNode.js/TypeScript向けのORMツール。
データベース操作を型安全に行えるらしいので、Supabaseと併用して導入してみる。
1. 依存関係のインストール
npm install prisma @prisma/client
2. Prismaの初期化
npx prisma init
3. 環境変数の設定
.envにDATABASE_URLを追記
DATABASE_URL="postgresql://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:5432/postgres"
画面上部の
Connectボタンから接続文字列を取得できた
トラブルメモ
npx prisma init実行時に以下の問題が発生:
-
.envファイルにDATABASE_URLが自動で追記されなかった -
prisma.config.tsという不要なファイルが生成された -
schema.prismaのproviderが"prisma-client"になっていた(正しくは"prisma-client-js")
原因は不明だが、手動で修正して対応。
Prismaスキーマの定義
prisma/schema.prismaにUserモデルを追加。
Prismaではユーザーの追加情報(名前、ロール等)のみを管理して、認証関係のことはSupabase Auth が上手いことやってくれるらしい。
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(uuid())
email String @unique
name String?
role UserRole @default(MEMBER)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
enum UserRole {
ADMIN
MEMBER
}
データベースに反映
サンプルで作成したテーブルをリセットしてから、正式なマイグレーションを実行。
# 既存のテーブルをすべて削除
npx prisma migrate reset
# 初回マイグレーションを実行
npx prisma migrate dev --name init
サンプル用に作成していた
app/instruments/page.tsx等のファイルも削除しておく。
Prisma Clientを作成
Prismaのクライアントを初期化するファイルを作成。
Next.jsなどの開発環境ではホットリロード時にコードが何度も再実行されるため、
接続が増えすぎないようシングルトンパターンで実装するらしい。
lib/prisma.ts
import { PrismaClient } from '@prisma/client';
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined;
};
export const prisma = globalForPrisma.prisma ?? new PrismaClient();
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;
コードの補足
globalThis(グローバルオブジェクト)には本来prismaプロパティは存在しないが、
as unknown asで無理やりキャストすることで、TypeScriptの型チェックを通している。
実行時に動的にプロパティを追加するためのテクニック。
// ❌ これはエラー(直接キャストできない)
globalThis as { prisma: PrismaClient | undefined }
// ✅ 一旦unknownを経由すれば何にでもキャストできる
globalThis as unknown as { prisma: PrismaClient | undefined }
Supabase Auth(認証機能)の統合
公式ドキュメントに沿って実装。
変更点:
ログイン後のページのディレクトリ名をprivateからdashboardに変更。
認証範囲を /dashboard 配下に限定。
- ディレクトリ名の変更
-
app/private/→app/dashboard
-
- ミドルウェアの修正
-
lib/supabase/middleware.ts// 認証が必要なページ(/dashboard配下) if (!user && request.nextUrl.pathname.startsWith('/dashboard')) { const url = request.nextUrl.clone(); url.pathname = '/login'; return NextResponse.redirect(url); } // ログイン済みユーザーが認証ページにアクセスした場合 if (request.nextUrl.pathname.startsWith('/login') && user) { const url = request.nextUrl.clone(); url.pathname = '/dashboard'; return NextResponse.redirect(url); }
-
- ログイン成功時のリダイレクト先を変更
-
app/login/actions.ts(2箇所)- redirect('/'); + redirect('/dashboard');
-
トラブルメモ
xxx@example.comのようなテスト用メールアドレスはSupabase側でバリデーションエラーになった。
回避策はあるらしいが、一旦実際のメールアドレスを使用することにした。
Gmailの+エイリアス機能を使うことで、同じ受信箱に届きつつ別アドレスとして登録できるらしいので、ひとまずこれで進めていく。
myaddress+1@gmail.com
myaddress+2@gmail.com
トラブルメモ
メール確認が必須になっていた
Supabaseでメール確認が有効になっており、確認メールのリンクをクリックしないとログインできなかった。
解決方法:
- Supabaseダッシュボードにアクセス
-
Authentication→ `Sign in / Provuders -
Confirm emailを OFF にする
追記:
気が付いたらxxx@example.comのようなテスト用メールアドレスが使用可能になっていた。
恐らく上記設定をしたおかげ。
Supabase AuthとPrismaの整合性問題
Supabase AuthからユーザーをWeb上で削除しても、Prismaには残ったままで整合性が取れず面倒だった。
対処法:
npx prisma studio
Prisma StudioのGUIからもユーザーを削除する必要がある。
ちゃんと削除処理を実装すれば解決するはず
ログイン/サインアップフォームのUI実装
認証機能が動くようになったので、UIを整える。
shadcn/ui の Cardコンポーネントを使って、ログイン・サインアップフォームを作成した。
まずはマニュアル通りにコンポーネントを追加。
その後、テキストを日本語に変更したり、ボタンやリンクの遷移先を自分の環境に合わせて微調整した。
Google認証を実装
shadcn/uiのマニュアル通りにログインフォームを実装したら、「Googleでログイン」というボタンがあった。
ということで、せっかくなのでGoogle認証も実装してみることにした。
1. Google Cloud Consoleにアクセス
Supabaseと同じプロジェクト名で作成しておいた。
2. OAuth同意画面の設定
- 左メニュー「APIとサービス」→「OAuth同意画面」
- 作成したプロジェクトを選択
- 「開始」ボタンをクリックし、アプリ情報を入力
- 対象に「外部」を選択
- 連絡先情報を入力して完了
3. OAuthクライアントの作成
- 「OAuthクライアントを作成」をクリック
- アプリケーションの種類を「ウェブアプリケーション」に設定
- 名前を入力し、承認済みリダイレクトURIにSupabaseのコールバックURLを追加
- IDを作成し、表示されたクライアントIDとクライアントシークレットを控えておく
4. SupabaseでGoogle認証を有効化
- Supabase Dashboardで「Authentication」→「Providers」→「Google」を開く
- 先ほどコピーしておいたクライアントIDとクライアントシークレットを入力
- 設定を保存して有効化
5. アプリケーションに認証処理を実装
最後にアプリケーションに認証周りの処理を実装し、Google認証が使えるようになった。
Supabaseは他にもGitHub、Discord、Twitterなど様々なプロバイダーに対応していて便利すぎる...
サイドバーの作成
ログイン後のダッシュボードを作るにあたり、共通のサイドバーを作ることにした。
とりあえずモックとして見た目だけ作成し、ダッシュボード全体に適用。
layout.tsxの階層構造
Next.jsではlayout.tsxをディレクトリごとに配置できることがわかった。
これにより、ログインページにはサイドバーを表示せず、ダッシュボード配下(/dashboard)だけに適用できる。
app/
├── layout.tsx # 全体共通レイアウト
├── login/
│ └── page.tsx # サイドバーなし
└── dashboard/
├── layout.tsx # サイドバー付きレイアウト
└── page.tsx # サイドバーあり
ディレクトリ構成も迷いにくく、中々便利な仕組み。
ダークモードの実装
shadcn/uiの公式ドキュメント通りにダークモードを実装。
next-themesを使ってライト/ダーク/システムの3つのテーマ切り替えに対応した。
マニュアル通りに進めるだけで特に詰まることなく実装完了。
テーマの切り替えトグルは、ドキュメントのサンプルをほぼほぼそのまま使っているが、後ほど変更予定。
ユーザーメニューの実装
テーマの切り替えトグルはユーザー設定画面に配置したいと考え、ユーザーメニューとユーザー設定ダイアログを実装。

コンポーネントをどこまで分けるかなど迷ったがこのような構成にした。
features/layout/components/
├── app-sidebar.tsx
└── user-menu/
├── index.tsx # UserMenuコンポーネント(統合)
├── account-settings-dialog.tsx # ダイアログコンテンツ
├── account-settings-item.tsx # Dialog + Trigger
└── logout-item.tsx # ログアウトメニューアイテム
index.tsxはディレクトリ名を使えるとのことで、コンポーネントの親子関係が管理しやすいと感じた。
別コンポーネントからの呼び出しもシンプル。
// app-sidebar.tsx
import { UserMenu } from './user-menu'; // user-menu/index.tsx が読み込まれる
<DropdownMenuContent side="top">
<UserMenu /> // シンプル!
</DropdownMenuContent>
