1. はじめに
-
本記事の目的:
Render × Vercel を使い、Spring Boot × React のフルスタックアプリを公開する方法を解説します。 -
追加要素:
GitHub Actions(CI/CD) で自動テスト・自動デプロイを行い、実務に近い形を再現しました。 -
想定読者:
• フルスタック学習中でデプロイまで経験したい人
• ポートフォリオを公開したい人
• CI/CD や Docker を導入してみたい人
2. 全体アーキテクチャと技術スタック
以下でまとめたSpring Boot × React アプリの内容を前提に構築します。
https://qiita.com/MiyaHamu86/items/704056a16e8081cead5f
| 区分 | 採用技術 / サービス | メモ |
|---|---|---|
| バックエンド | Spring Boot + Gradle + PostgreSQL | |
| フロントエンド | React + Vite + TypeScript | |
| インフラ | Render(Backend / DB)+ Vercel(Frontend) | RenderにAPI/DB、Vercelに静的フロント |
| 開発フロー | GitHub Actions(CI/CD)、Docker(コンテナ化) | CIテスト/ビルド自動化、環境の共通化 |
3. GitHub Actions による CI/CD 構築
CI: バックエンドのビルド・テスト(JUnit)を自動化。
CD: Render Deploy Hook / Vercel Hook を GitHub Actions から呼び出して自動デプロイ。
以下、CI/CD用の設定ファイルを準備します。
name: backend-ci
on:
push:
paths:
- 'todo-backend/**'
- '.github/workflows/backend-ci.yml'
pull_request:
paths:
- 'todo-backend/**'
jobs:
build:
runs-on: ubuntu-latest
defaults:
run:
working-directory: todo-backend
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'
- name: Grant execute permission for gradlew
run: chmod +x ./gradlew
# テスト込みビルド(-x test を外す)
- name: Build (with tests)
run: ./gradlew clean build
# テスト成功 & main への push のときだけ Render を起動
- name: Trigger Render Deploy
if: github.event_name == 'push' && github.ref == 'refs/heads/main' && success()
run: curl -sSf -X POST "${{ secrets.RENDER_DEPLOY_HOOK }}"
name: frontend-ci
on:
push:
paths:
- 'todo-frontend/**'
- '.github/workflows/frontend-ci.yml'
pull_request:
paths:
- 'todo-frontend/**'
jobs:
build:
runs-on: ubuntu-latest
defaults:
run:
working-directory: todo-frontend
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm run build
4. バックエンド(Spring Boot)を Render にデプロイ
Render の「Native Runtime」でJavaは対象外のため、Web Service を Docker で立てる必要があります。
#---- Build stage ----
FROM gradle:8.8-jdk21 AS build
WORKDIR /app
COPY build.gradle settings.gradle ./
# 動作確認(任意)
RUN gradle --version
# 残りのソース
COPY src ./src
# ビルド
RUN gradle clean bootJar -x test
# ---- Run stage ----
FROM eclipse-temurin:21-jre
WORKDIR /app
EXPOSE 8081
COPY --from=build /app/build/libs/app.jar /app/app.jar
ENTRYPOINT ["sh","-c","java $JAVA_OPTS -jar /app/app.jar"]
現在は、Render完結のDockerfile構成ですが、
今後の課題として、GitHub Actions で Docker イメージをビルド&レジストリへ push
→ Render はその 出来上がったイメージを pull して起動する運用に挑戦したいと考えています。
■WEBサービス作成手順
Renderにサインインします。
1.Renderダッシュボード右上「+New」押下
2.「WebService」押下
3.「PublicGitRepository」タブにGitリポジトリURLを入力し、「Connect」押下
4.以下入力する。
| 設定項目 | 設定値 | メモ |
|---|---|---|
| Instance Type | Free | |
| Environment Variables | 後ほど設定 | 必要な環境変数は別セクションで定義・投入 |
| Auto-Deploy | Off | GitHub ActionsでCI/CD実行後に手動/自動デプロイを実施 |
5.「Deploy Web Service」押下
■PostgreSQL サービス作成手順
1.Renderダッシュボード右上「+New」押下
2.「Postgres」を押下し、以下を設定
Name:任意
Plan Options
Instance Type : Free
3.「Create DataBase」押下
■Deploy Hook設定
GithubAction(CI)でのテスト合格後、Renderでデプロイさせる設定です。
# テスト成功 & main への push のときだけ Render を起動
- name: Trigger Render Deploy
if: github.event_name == 'push' && github.ref == 'refs/heads/main' && success()
run: curl -sSf -X POST "${{ secrets.RENDER_DEPLOY_HOOK }}"
1) Render で Deploy Hook URL を取得
1.WEB SERVICE画面で Settings 押下。
2.Build & Deploy Deploy Hooks → Create Hook押下
3.表示された URL をコピー
2) GitHub Secrets に保存
1.GitHub → リポジトリ → Settings → Secrets and variables → Actions → New repository secret
2.以下を入力
Name: RENDER_DEPLOY_HOOK
Value: さきほどの URL
3.Add Secret押下
5. フロントエンド(React/Vite)を Vercel にデプロイ
Vercel のリポジトリ連携とビルド設定
Vercelにサインイン
1.ホーム画面「▲Start Deploying」押下
2.Import Git Repositoryにて、GitリポジトリURL指定
3.New Projectにて、以下を設定
| 設定項目 | 設定値 | メモ |
|---|---|---|
| Root Directory | todo-frontend | フロント用のディレクトリ |
| Install Command | npm install | 依存関係をインストール |
| Build Command | npm run build | Viteビルド(出力先は通常 dist) |
■Environment Variables
| 環境変数 Key | Value | メモ |
|---|---|---|
| VITE_API_BASE_URL | / | 相対ベースURL。呼び出しは /api/... を使用(Vercel rewrites / Vite proxy) |
4.Deploy押下
Vercelのrewritesで /api をRenderへプロキシ
vercel.json の rewrites 設定で /api でバックエンドAPI を透過的に呼び出します。
API ベースURLを設定します。
{
"rewrites": [
{ "source": "/api/(.*)", "destination": "https://todo-java-react.onrender.com/$1" },
{ "source": "/(.*)", "destination": "/" }
]
}
RenderのWebサービス画面上部に、バックエンドAPIのURLがあります。

Vercelの自動デプロイ
VercelはGit連携した場合、デフォルトでプッシュ時に自動デプロイされます。
以下の設定で、指定したフロント用のディレクトリに変更があった場合のみ、自動デプロイ設定が可能です。

また、以下で、ビルドする条件を細かく設定して、自動デプロイの制御が可能です。
Project → Settings → Git → Ignored Build Step
6.Render側へ環境変数設定
1.WEB SERVICE画面で Environment 押下。
2.Environment Variablesに以下を設定。
| 環境変数 Key | Value | メモ |
|---|---|---|
| CORS_ALLOWED_ORIGINS | https://todo-java-react.vercel.app/ |
| 環境変数 Key | Value | メモ |
|---|---|---|
| DATABASE_URL | jdbc:postgresql://dpg-d3381u6mcj7s73a56p30-a.oregon-postgres.render.com/tododb_nl4r?sslmode=require | JDBC接続URL(SSL必須) |
| DB | tododb_nl4r | データベース名 |
| DB_USERNAME | tododb_nl4r_user | DBユーザー名 |
| DB_PASSWORD | ******** | DBパスワード |
| HOST | dpg-d3381u6mcj7s73a56p30-a | DBホスト名 |
| 環境変数 Key | Value | メモ |
|---|---|---|
| PORT | 5432 | |
| JWT_SECRET | ******** |
JWT_SECRET→Base64 でエンコードしたランダム文字列を使います。
以下で作成できます。
[Convert]::ToBase64String((1..64 | ForEach-Object {Get-Random -Maximum 256}))
7. テスト自動化の実践
バックエンド:JUnit テストを Actions 上で実行。
Beanの依存関係の解決に苦戦したため、SpringBootを起動せず、依存するモックを定義して
簡単なAPIレスポンスのテストを行いました。
class PublicApiSmokeTest {
private MockMvc mvc;
// 依存はすべて Mockito のモック
private JwtService jwt;
private UserMapper userMapper;
private PasswordEncoder passwordEncoder;
private AuthenticationManagerBuilder amb;
@BeforeEach
void setup() {
jwt = mock(JwtService.class);
userMapper = mock(UserMapper.class);
passwordEncoder = mock(PasswordEncoder.class);
amb = mock(AuthenticationManagerBuilder.class);
AuthController controller = new AuthController(amb, jwt, userMapper, passwordEncoder);
mvc = MockMvcBuilders.standaloneSetup(controller)
.build();
}
/** ボディなしで /auth/login を叩いたら 400 を返す(存在確認+バリデーション) */
@Test
void login_requires_body_returns_400() throws Exception {
mvc.perform(post("/auth/login").contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isBadRequest());
}
/** /auth/register に正しいJSONを投げると、パスワードをエンコードして UserMapper.insert が呼ばれる */
@Test
void register_encodes_password_and_inserts_user() throws Exception {
when(passwordEncoder.encode("pass")).thenReturn("ENC(pass)");
mvc.perform(
post("/auth/register")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"username\":\"testuser\",\"password\":\"pass\",\"roles\":\"1\"}")
).andExpect(status().isOk());
//コントローラ内で new された User はこちらから参照できないので、キャプチャして中身をチェックする
ArgumentCaptor<User> captor = ArgumentCaptor.forClass(User.class);
//userMapper.insert(...) が 1回だけ呼ばれたことを検証
verify(userMapper, times(1)).insert(captor.capture());
User saved = captor.getValue();
//testuserがJSONでPOSTされたことの確認
assertEquals("testuser", saved.getUsername());
//passwordEncoder.encode("pass")が呼ばれたことの確認
assertEquals("ENC(pass)", saved.getPassword());
}
}
フロントエンド:(省略)
今回は割愛しますが、Reactでは以下のようなCIテストができるそうです。
- 静的チェック:ESLint + TypeScript(型崩れ/書き方ミス)
- 単体/コンポーネントテスト:Vitest + React Testing Library
- E2E:Playwright(実際のブラウザを自動操作して確認)
CI 上でテストが落ちるとデプロイされない仕組み。
1.GitHub Actions のジョブにて、./gradlew clean buildの中でテストが走る。
2.JUnit が@Testのついたクラスを実行
3.Testクラスのアサーション(処理結果が期待値通りかチェック)が失敗
4.Gradleタスクがエラー終了
5.GitHub Actions のジョブ失敗(RenderへのDeployHookが実行されない)
8. まとめ
- Render × Vercel × GitHub Actions × Docker を組み合わせることで、実務に近いフルスタック開発環境が体験できる。
- CI/CD + テスト自動化で品質と効率を両立できる。
以下、実際に触れるデモ環境とソースコードです。
🔗 デモURL:https://todo-java-react.vercel.app
※バックエンドAPI初回接続時、Renderサーバがスリープモードから起動するため2分程度かかります。15分動作がないと再度スリープモードへ移ります。
💻 GitHub:https://github.com/miyagawa-git/todo-java-react
ご意見・ご指摘などありましたらぜひコメントでいただけると嬉しいです。


