📑

Next.jsとSpringのモノレポ構築メモ(お蔵入り)

2024/12/24に公開

1. 環境

  • maxOS Venture 13.7.2

2. Node.jsをインストール

brewでnodebrewをインストール

brew install nodebrew
/usr/local/opt/nodebrew/bin/nodebrew setup_dirs
echo 'export PATH=$HOME/.nodebrew/current/bin:$PATH' >> ~/.zshrc

nodebrewでNode.jsをインストール

nodebrew install v22.12.0
nodebrew use v22.12.0

3. 新規プロジェクト作成

リポジトリの初期化

まず、新しいGitリポジトリを作成します。

PROJECT_HOME=`pwd`/my-project
mkdir -p $PROJECT_HOME
cd $PROJECT_HOME
git init

パッケージマネージャーの選定と初期化

ここでは pnpm を使用する前提で進めます。

pnpmのインストールと初期化

# pnpmのインストール(まだインストールしていない場合)
npm install -g pnpm

# pnpmワークスペースの初期化
pnpm init

package.jsonを編集

package.json

{
  "name": "my-project",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "packageManager": "pnpm@9.15.1",
  "scripts": {
  "build": "turbo run build",
    "lint": "turbo run lint",
    "test": "turbo run test",
    "deploy": "turbo run deploy"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "turbo": "^2.3.3"
  }
}

Turborepoのセットアップ

Turborepoをインストールし、設定ファイルを作成します。

pnpm add -D turbo

turbo.json

{
  "$schema": "https://turbo.build/schema.json",
  "tasks": {
    "lint": {
      "outputs": []
    },
    "typecheck": {
      "dependsOn": ["lint"],
      "outputs": []
    },
    "test": {
      "dependsOn": ["typecheck"],
      "outputs": ["**/coverage/**"]
    },
    "build": {
      "dependsOn": ["test", "^build"],
      "outputs": [".next/**", "out/**", "dist/**", "build/**", "target/**"]
    },
    "export": {
      "dependsOn": ["build"],
      "outputs": ["out/**"]
    },
    "deploy": {
      "dependsOn": ["export"],
      "outputs": [],
      "cache": false
    }
  }
}

ワークスペース設定

ほわっつ ワークスペース

pnpm-workspace.yaml を作成し、以下を追加します。

packages:
  - "apps/*"
  - "packages/*"
  - "infrastructure/*"
  - "config/*"

ディレクトリ構成の作成

ディレクトリ構造を作成します。

mkdir -p apps/web apps/api apps/batch packages/openapi packages/shared-ts-utils packages/shared-java-utils infrastructure/docker infrastructure/k8s infrastructure/terraform config scripts

ここまでこんな感じ

.
├── apps
│   ├── api
│   ├── batch
│   └── web
├── config
├── infrastructure
│   ├── docker
│   ├── k8s
│   └── terraform
├── node_modules
│   └── turbo -> .pnpm/turbo@2.3.3/node_modules/turbo
├── package.json
├── packages
│   ├── openapi
│   ├── shared-java-utils
│   └── shared-ts-utils
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── scripts
└── turbo.json

17 directories, 4 files

各アプリケーションの初期設定

フロントエンド(Next.js/TypeScript)のセットアップ

apps/web ディレクトリに移動し、Next.jsアプリを作成します。ここでは create-next-app を使用します。

cd $PROJECT_HOME/apps/web
pnpm create next-app@latest . --typescript --no-tailwind --eslint --app --src-dir --import-alias "@/*" --use-pnpm --no-turbopack

必要なパッケージを追加します。

pnpm add -D eslint prettier eslint-config-prettier eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-jsx-a11y @typescript-eslint/parser @typescript-eslint/eslint-plugin
pnpm add redux @reduxjs/toolkit react-redux
pnpm add -D jest @testing-library/react @testing-library/jest-dom
pnpm add next-pwa

next.config.js の設定

PWA機能を有効にするために next-pwa を設定します。

apps/web/next.config.js

// apps/web/next.config.js
import withPWA from 'next-pwa';

export default withPWA({
  dest: 'public',
  disable: process.env.NODE_ENV === 'development',
  // 他のNext.js設定
  exportTrailingSlash: true,
  trailingSlash: true,
  reactStrictMode: true,
  // 必要に応じて他の設定を追加
});

package.json のモジュールタイプ設定

ES6モジュールを使用するために、package.json に "type": "module" を追加します。

// apps/web/package.json
{
  "name": "web",
  "version": "0.1.0",
  "private": true,
  "type": "module",
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "export": "next export",
    "build-and-export": "next build && next export",
    "start": "next start",
    "test": "jest",
    "deploy": "pnpm run build-and-export && pnpm run deploy"
  },
  // 他の設定
}


### サーバーサイド(Java/Spring)のセットアップ

apps/api ディレクトリに移動し、Spring Bootプロジェクトをセットアップします。ここでは Spring Initializr を使用します。

```zsh
cd $PROJECT_HOME/apps/api
curl https://start.spring.io/starter.zip \
  -d dependencies=web,data-jpa,security,actuator \
  -d javaVersion=21 \
  -d type=gradle-project-kotlin \
  -d groupId=com.example \
  -d artifactId=api \
  -o api.zip
unzip api.zip -d .
rm api.zip

必要な依存関係を追加します。build.gradle を編集し、springdoc-openapi や micrometer などのライブラリを追加します。

build.gradle.kts ファイルの implements の配下に以下を追加

implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0")
implementation(platform("io.micrometer:micrometer-bom:1.14.2"))
implementation("implementation 'io.micrometer:micrometer-registry-datadog")

共有パッケージのセットアップ

OpenAPIスキーマ管理

packages/openapi にOpenAPIスキーマとコード生成設定を追加します。

cd $PROJECT_HOME/packages/openapi
mkdir codegen generated
# スキーマファイルを `schema.yaml` として追加
touch schema.yaml

共有TypeScriptユーティリティ

packages/shared-ts-utils を初期化します。

cd $PROJECT_HOME/packages/shared-ts-utils
pnpm init
pnpm add -D typescript
touch tsconfig.json  # 設定はあとで

tsconfig.json の設定

`packages/shared-ts-utils/tsconfig.json

{
  "compilerOptions": {
    "target": "ES6",
    "module": "ES6",
    "declaration": true,
    "outDir": "dist",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  },
  "include": ["src"]
}

共有Javaユーティリティ

packages/shared-java-utils を初期化します。

cd $PROJECT_HOME/packages/shared-java-utils
curl https://start.spring.io/starter.zip \
  -d javaVersion=21 \
  -d type=gradle-project-kotlin \
  -d groupId=com.example \
  -d artifactId=shared-java-utils \
  -o shared-java-utils.zip
unzip shared-java-utils.zip -d .
rm shared-java-utils.zip

インフラ設定

Docker設定

infrastructure/docker に各アプリケーションの Dockerfile を作成します。

フロントエンドのDockerfile

Next.js用のDockerfileを作成します。PWA機能を有効にするための設定も含めます。

ここ、本番ステージのところ後で確認

infrastructure/docker/apps/web/Dockerfile

# apps/web/Dockerfile
# ビルドステージ
FROM node:18-alpine AS builder
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN pnpm install
COPY . .
RUN pnpm run build-and-export

# 本番ステージ
FROM nginx:alpine
COPY --from=builder /app/out /usr/share/nginx/html
COPY --from=builder /app/public /usr/share/nginx/html/public
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

サーバーサイドのDockerfile

infrastructure/docker/apps/api/Dockerfile

FROM openjdk:21-jdk-alpine
VOLUME /tmp
COPY build/libs/api-0.0.1-SNAPSHOT.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

Docker Composeの設定

infrastructure/docker/docker-compose.yml を作成します。

ymlのバージョンが古そうなので確認

name: my-web

services:
  api:
    build:
      context: ../../apps/api
      dockerfile: Dockerfile
    ports:
      - "8080:8080"
    environment:
      SPRING_PROFILES_ACTIVE: dev
      AWS_COGNITO_CLIENT_ID: your_client_id
      AWS_COGNITO_REGION: your_region
    depends_on:
      - db

  web:
    build:
      context: ../../apps/web
      dockerfile: Dockerfile
    ports:
      - "80:80" # Nginxがポート80をリッスン
    environment:
      NODE_ENV: production
      NEXT_PUBLIC_API_URL: http://localhost:8080/api
    depends_on:
      - api

  db:
    image: postgres:13
    environment:
      POSTGRES_USER: your_user
      POSTGRES_PASSWORD: your_password
      POSTGRES_DB: your_db
    ports:
      - "5432:5432"
    volumes:
      - db-data:/var/lib/postgresql/data

volumes:
  db-data:

CI/CD設定(参考)

共通設定ファイルの追加

ESLintとPrettier設定 (config/ ディレクトリ)

config/eslint.config.js を作成します。

フォーマットが古いっぽいんであとでかくにん

// config/eslint.config.js
export default {
  parser: '@typescript-eslint/parser',
  extends: [
    'eslint:recommended',
    'plugin:react/recommended',
    'plugin:@typescript-eslint/recommended',
    'prettier'
  ],
  plugins: ['react', '@typescript-eslint'],
  env: {
    browser: true,
    es2021: true,
    node: true
  },
  settings: {
    react: {
      version: 'detect'
    }
  },
  rules: {
    // カスタムルールを追加
  }
};

config/prettier.config.js を作成します。

Turborepoスクリプト (scripts/ ディレクトリ)

必要に応じて、共通のビルドやデプロイスクリプトを scripts/ ディレクトリに追加します。

内容はあとで精査

# scripts/build-all.sh
#!/bin/bash
pnpm turbo run build
# scripts/deploy-web.sh
#!/bin/bash

# 必要な環境変数の確認
if [ -z "$CMS_API_ENDPOINT" ] || [ -z "$CMS_API_KEY" ]; then
  echo "必要な環境変数が設定されていません。"
  exit 1
fi

# 静的ファイルのビルドとエクスポート
pnpm --filter web run build-and-export

# CMSへのアップロード(例: Headless CMSのAPIを使用)
# ここでは、一般的なHTTP POSTリクエストを使用した例を示します。具体的なCMSに応じて適宜調整してください。
curl -X POST "$CMS_API_ENDPOINT" \
  -H "Authorization: Bearer $CMS_API_KEY" \
  -F "file=@apps/web/out/*" \
  -F "path=/desired/path/on/cms"

echo "フロントエンドのデプロイが完了しました。"

スクリプトに実行権限を付与します。

chmod +x scripts/*.sh

リポジトリの運用方法

モノレポ構成の維持

  • ディレクトリ構成の遵守: apps/ 配下にアプリケーション、packages/ 配下に共有ライブラリ、infrastructure/ にインフラ関連の設定を配置します。
  • 共有コードの再利用: packages/shared-ts-utils や packages/shared-java-utils などの共有ライブラリを活用し、コードの重複を避けます。

Turborepoによるタスク管理

turbo.json に定義されたタスクを活用して、ビルド、テスト、エクスポート、デプロイなどを効率的に実行します。

主なタスクの実行例

  • Lintの実行
pnpm turbo run lint
  • ビルドとエクスポートの実行
pnpm turbo run build
pnpm turbo run export

- デプロイの実行

pnpm turbo run deploy --filter web

Discussion