ANTIGRAVITY LABEN
記事一覧/アプリ開発
アプリ開発/2026-03-10中級

Next.js × Antigravity Web 開発ガイド — AI 駆動のフルスタック開発

Antigravity を活用して Next.js アプリケーションを効率的に開発する方法を、プロジェクト作成からデプロイまで解説します。

Next.js6Web開発3フルスタック3React3Cloudflare2

取り組みの背景

Next.js は React ベースのフルスタック Web フレームワークとして、サーバーサイドレンダリング (SSR)、静的生成 (SSG)、API ルート、そして最新の App Router など、モダンな Web 開発に必要な機能を備えています。

Antigravity と組み合わせることで、Next.js の開発効率をさらに飛躍的に向上させることができます。自動コンポーネント生成、API ロジック実装の自動化、テストコード生成など、開発の各段階での効率化が可能です。

ここでAntigravity を活用した Next.js プロジェクトの構築方法を、セットアップからデプロイまで、実践的に解説します。

Next.js + Antigravity プロジェクトの初期設定

プロジェクト構造の計画

# Antigravity 用にプロジェクト構造を最適化した Next.js プロジェクト作成
my-app/
├── app/                      # App Router (メインアプリケーション)
   ├── layout.tsx           # ルートレイアウト
   ├── page.tsx             # ホームページ
   ├── api/                 # API ルート
   ├── users/
   ├── posts/
   └── auth/
   └── dashboard/           # 認証済みユーザー向けページ
├── components/              # 再利用可能なコンポーネント
   ├── ui/                 # UI コンポーネント (Button, Card, etc)
   ├── layouts/            # レイアウトコンポーネント
   └── features/           # 機能別コンポーネント
├── lib/                    # ユーティリティと設定
   ├── db.ts              # データベース接続
   ├── auth.ts            # 認証ロジック
   ├── utils.ts           # ヘルパー関数
   └── types.ts           # TypeScript 型定義
├── styles/                # グローバルスタイル
├── public/                # 静的ファイル
├── prisma/               # ORM スキーマ
├── .env.local            # 環境変数
└── next.config.js        # Next.js 設定

プロジェクト作成と依存関係インストール

# Next.js プロジェクトを作成
npx create-next-app@latest my-app --typescript --tailwind --eslint
 
# 必要なライブラリをインストール
npm install prisma @prisma/client
npm install bcryptjs jsonwebtoken
npm install axios zod
npm install -D @types/node @types/react

Antigravity との統合設定

Antigravity でコード生成を効率化するために、プロンプトテンプレートを設定します:

# Antigravity System Prompt for Next.js Development
 
あなたは Next.js フルスタック開発の専門家です。
 
以下のベストプラクティスに従ってください:
 
## コンポーネント生成
- React Server Components (RSC) を優先的に使用
- "use client" ディレクティブは対話的なコンポーネントのみに使用
- TypeScript で型を明確に定義
- Tailwind CSS でスタイルを管理
 
## API ルート実装
- 入力検証には Zod を使用
- エラーハンドリングは try-catch で適切に
- 認証チェックを実装
- レスポンスは JSON 形式で統一
 
## データベース
- Prisma ORM を使用
- マイグレーションは自動管理
- 関連する型情報を含める
 
## セキュリティ
- 環境変数で機密情報を管理
- CSRF 対策を実装
- 入力値の検証を厳密に

App Router を使用したプロジェクト構築

レイアウト構造の実装

// app/layout.tsx
import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import '@/styles/globals.css';
import Navbar from '@/components/layouts/Navbar';
import Footer from '@/components/layouts/Footer';
 
const inter = Inter({ subsets: ['latin'] });
 
export const metadata: Metadata = {
  title: 'My App',
  description: 'Generated with Antigravity + Next.js',
  icons: {
    icon: '/favicon.ico',
  },
};
 
export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="ja">
      <body className={inter.className}>
        <Navbar />
        <main className="min-h-screen">
          {children}
        </main>
        <Footer />
      </body>
    </html>
  );
}

ホームページの実装

// app/page.tsx
import Link from 'next/link';
import Button from '@/components/ui/Button';
 
export default function Home() {
  return (
    <div className="container mx-auto px-4 py-16">
      <div className="max-w-2xl mx-auto text-center">
        <h1 className="text-5xl font-bold mb-6">
          Welcome to My App
        </h1>
        <p className="text-xl text-gray-600 mb-8">
          Powered by Antigravity + Next.js
        </p>
 
        <div className="flex gap-4 justify-center">
          <Link href="/about">
            <Button variant="primary">Learn More</Button>
          </Link>
          <Link href="/dashboard">
            <Button variant="outline">Dashboard</Button>
          </Link>
        </div>
      </div>
    </div>
  );
}

Antigravity でのコンポーネント生成

UI コンポーネントの自動生成

Antigravity プロンプト例:

次の UI コンポーネントを TypeScript で実装してください:
 
コンポーネント: BlogCard
説明: ブログ記事を表示するカードコンポーネント
 
仕様:
- Props: title, excerpt, author, publishDate, imageUrl, slug
- スタイル: Tailwind CSS で実装
- ホバーエフェクト: スケールアップとシャドウ追加
- リンク: 記事詳細ページへのリンク含む
- レスポンシブ: モバイル対応
 
出力形式: 完全な React コンポーネントコード

生成されたコンポーネントの例

// components/ui/BlogCard.tsx
'use client';
 
import Image from 'next/image';
import Link from 'next/link';
import { formatDate } from '@/lib/utils';
 
interface BlogCardProps {
  title: string;
  excerpt: string;
  author: string;
  publishDate: Date;
  imageUrl: string;
  slug: string;
}
 
export default function BlogCard({
  title,
  excerpt,
  author,
  publishDate,
  imageUrl,
  slug,
}: BlogCardProps) {
  return (
    <Link href={`/blog/${slug}`}>
      <article className="group cursor-pointer">
        <div className="relative overflow-hidden rounded-lg mb-4 bg-gray-100">
          <Image
            src={imageUrl}
            alt={title}
            width={400}
            height={250}
            className="w-full h-[250px] object-cover group-hover:scale-105 transition-transform duration-300"
          />
        </div>
 
        <div className="group-hover:shadow-lg transition-shadow p-4 -mx-4">
          <h3 className="text-xl font-bold mb-2 group-hover:text-blue-600">
            {title}
          </h3>
          <p className="text-gray-600 mb-3 line-clamp-2">
            {excerpt}
          </p>
          <div className="flex justify-between text-sm text-gray-500">
            <span>{author}</span>
            <span>{formatDate(publishDate)}</span>
          </div>
        </div>
      </article>
    </Link>
  );
}

API ルートの実装

ユーザー API の実装例

Antigravity プロンプト:

Next.js API ルート を実装してください
 
機能: ユーザー一覧取得 API
 
仕様:
- エンドポイント: GET /api/users
- 認証: JWT トークン必須
- クエリパラメータ: page=1, limit=10
- レスポンス: ユーザーオブジェクトの配列
- エラーハンドリング: 認証失敗は 401, サーバーエラーは 500
- バリデーション: Zod で入力値検証

実装コード

// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
import { verifyAuth } from '@/lib/auth';
import { db } from '@/lib/db';
 
// クエリパラメータの検証スキーマ
const querySchema = z.object({
  page: z.string().regex(/^\d+$/).optional().default('1'),
  limit: z.string().regex(/^\d+$/).optional().default('10'),
});
 
export async function GET(request: NextRequest) {
  try {
    // 認証チェック
    const user = await verifyAuth(request);
    if (!user) {
      return NextResponse.json(
        { error: 'Unauthorized' },
        { status: 401 }
      );
    }
 
    // クエリパラメータの検証
    const url = new URL(request.url);
    const query = querySchema.parse({
      page: url.searchParams.get('page'),
      limit: url.searchParams.get('limit'),
    });
 
    const page = parseInt(query.page);
    const limit = parseInt(query.limit);
    const skip = (page - 1) * limit;
 
    // データベースクエリ
    const [users, total] = await Promise.all([
      db.user.findMany({
        skip,
        take: limit,
        select: {
          id: true,
          name: true,
          email: true,
          createdAt: true,
        },
      }),
      db.user.count(),
    ]);
 
    return NextResponse.json({
      users,
      pagination: {
        page,
        limit,
        total,
        pages: Math.ceil(total / limit),
      },
    });
  } catch (error) {
    console.error('Users API error:', error);
 
    if (error instanceof z.ZodError) {
      return NextResponse.json(
        { error: 'Invalid parameters', details: error.errors },
        { status: 400 }
      );
    }
 
    return NextResponse.json(
      { error: 'Internal server error' },
      { status: 500 }
    );
  }
}

認証の実装

JWT ベースの認証システム

// lib/auth.ts
import jwt from 'jsonwebtoken';
import bcryptjs from 'bcryptjs';
import { NextRequest } from 'next/server';
 
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
 
export interface AuthPayload {
  userId: string;
  email: string;
}
 
export function generateToken(payload: AuthPayload): string {
  return jwt.sign(payload, JWT_SECRET, { expiresIn: '7d' });
}
 
export function verifyToken(token: string): AuthPayload | null {
  try {
    return jwt.verify(token, JWT_SECRET) as AuthPayload;
  } catch {
    return null;
  }
}
 
export async function verifyAuth(request: NextRequest): Promise<AuthPayload | null> {
  const authHeader = request.headers.get('authorization');
  if (!authHeader?.startsWith('Bearer ')) {
    return null;
  }
 
  const token = authHeader.slice(7);
  return verifyToken(token);
}
 
export async function hashPassword(password: string): Promise<string> {
  return bcryptjs.hash(password, 10);
}
 
export async function comparePasswords(
  password: string,
  hash: string
): Promise<boolean> {
  return bcryptjs.compare(password, hash);
}

ログイン API

// app/api/auth/login/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
import { db } from '@/lib/db';
import { generateToken, comparePasswords } from '@/lib/auth';
 
const loginSchema = z.object({
  email: z.string().email(),
  password: z.string().min(6),
});
 
export async function POST(request: NextRequest) {
  try {
    const body = await request.json();
    const { email, password } = loginSchema.parse(body);
 
    const user = await db.user.findUnique({
      where: { email },
    });
 
    if (!user) {
      return NextResponse.json(
        { error: 'Invalid credentials' },
        { status: 401 }
      );
    }
 
    const isValidPassword = await comparePasswords(password, user.passwordHash);
    if (!isValidPassword) {
      return NextResponse.json(
        { error: 'Invalid credentials' },
        { status: 401 }
      );
    }
 
    const token = generateToken({
      userId: user.id,
      email: user.email,
    });
 
    return NextResponse.json({
      token,
      user: {
        id: user.id,
        name: user.name,
        email: user.email,
      },
    });
  } catch (error) {
    if (error instanceof z.ZodError) {
      return NextResponse.json(
        { error: 'Invalid input', details: error.errors },
        { status: 400 }
      );
    }
 
    return NextResponse.json(
      { error: 'Internal server error' },
      { status: 500 }
    );
  }
}

Tailwind CSS でのスタイリング

グローバルスタイルと Tailwind 設定

/* styles/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
 
@layer base {
  html {
    @apply scroll-smooth;
  }
 
  body {
    @apply bg-white dark:bg-slate-950 text-gray-900 dark:text-gray-50;
  }
}
 
@layer components {
  .btn {
    @apply px-4 py-2 rounded-lg font-medium transition-colors;
  }
 
  .btn-primary {
    @apply bg-blue-600 text-white hover:bg-blue-700;
  }
 
  .btn-outline {
    @apply border border-gray-300 text-gray-700 hover:bg-gray-50;
  }
 
  .container-tight {
    @apply max-w-4xl mx-auto px-4;
  }
}

Tailwind 設定ファイル

// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    './app/**/*.{js,ts,jsx,tsx}',
    './components/**/*.{js,ts,jsx,tsx}',
  ],
  theme: {
    extend: {
      colors: {
        brand: {
          50: '#f5f3ff',
          600: '#7c3aed',
          900: '#3e1d56',
        },
      },
      fontFamily: {
        sans: ['var(--font-inter)', 'system-ui'],
      },
    },
  },
  plugins: [],
};

Cloudflare/Vercel へのデプロイ

Vercel へのデプロイ

# Vercel CLI のインストール
npm install -g vercel
 
# プロジェクトをデプロイ
vercel
 
# 本番環境にデプロイ
vercel --prod

環境変数の設定

# .env.local(開発環境)
DATABASE_URL="postgresql://user:password@localhost:5432/myapp"
JWT_SECRET="your-secret-key"
NEXTAUTH_SECRET="another-secret"
 
# Vercel ダッシュボードで本番環境変数を設定
# Project Settings > Environment Variables

Cloudflare Pages へのデプロイ

# Wrangler CLI をインストール
npm install -g wrangler
 
# Cloudflare アカウントでログイン
wrangler login
 
# デプロイ
wrangler pages deploy ./out

パフォーマンス最適化

画像最適化

// components/OptimizedImage.tsx
import Image from 'next/image';
 
interface OptimizedImageProps {
  src: string;
  alt: string;
  width: number;
  height: number;
}
 
export default function OptimizedImage({
  src,
  alt,
  width,
  height,
}: OptimizedImageProps) {
  return (
    <Image
      src={src}
      alt={alt}
      width={width}
      height={height}
      priority={false}
      loading="lazy"
      placeholder="blur"
      blurDataURL="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
    />
  );
}

API キャッシング

// lib/cache.ts
export async function getCachedData<T>(
  key: string,
  fetchFn: () => Promise<T>,
  ttl: number = 3600
): Promise<T> {
  const cached = await redis.get(key);
 
  if (cached) {
    return JSON.parse(cached);
  }
 
  const data = await fetchFn();
  await redis.set(key, JSON.stringify(data), 'EX', ttl);
 
  return data;
}

テストの実装

API テストの例

// __tests__/api/users.test.ts
import { GET } from '@/app/api/users/route';
import { NextRequest } from 'next/server';
 
describe('GET /api/users', () => {
  it('should return 401 without auth', async () => {
    const request = new NextRequest('http://localhost:3000/api/users');
    const response = await GET(request);
 
    expect(response.status).toBe(401);
  });
 
  it('should return users with valid auth', async () => {
    const request = new NextRequest('http://localhost:3000/api/users', {
      headers: {
        'Authorization': 'Bearer valid-token',
      },
    });
 
    const response = await GET(request);
    expect(response.status).toBe(200);
 
    const data = await response.json();
    expect(data).toHaveProperty('users');
    expect(Array.isArray(data.users)).toBe(true);
  });
});

ベストプラクティス

1. Server Components の活用

// app/posts/page.tsx
import { db } from '@/lib/db';
import PostCard from '@/components/PostCard';
 
// Server Component: サーバーでのみ実行
export default async function PostsPage() {
  const posts = await db.post.findMany({
    orderBy: { createdAt: 'desc' },
  });
 
  return (
    <div className="grid gap-6">
      {posts.map(post => (
        <PostCard key={post.id} post={post} />
      ))}
    </div>
  );
}

2. 環境変数の管理

# .env.local
DATABASE_URL=
JWT_SECRET=
NEXTAUTH_SECRET=
 
# .env.production
DATABASE_URL=
JWT_SECRET=

3. エラーハンドリング

// app/error.tsx
'use client';
 
export default function Error({
  error,
  reset,
}: {
  error: Error;
  reset: () => void;
}) {
  return (
    <div className="flex flex-col items-center justify-center min-h-screen">
      <h1 className="text-2xl font-bold mb-4">Something went wrong</h1>
      <p className="text-gray-600 mb-6">{error.message}</p>
      <button
        onClick={reset}
        className="px-4 py-2 bg-blue-600 text-white rounded"
      >
        Try again
      </button>
    </div>
  );
}

まとめ

Antigravity と Next.js の組み合わせは、モダン Web アプリケーション開発を大幅に加速させます。

主なメリット:

  • 自動コンポーネント生成で開発時間を削減
  • API ロジックの自動生成で実装の効率化
  • TypeScript による型安全性の確保
  • テストコード生成で品質向上

これらのテクニックを活用することで、高速で保守性の高い Next.js アプリケーションを構築できます。

個人開発12年の現場で実感したこと

線引きするときの3つの判断軸

  • 失敗時の影響が金銭やユーザー体験にどれだけ波及するか
  • 復旧オペレーションが明文化されているか
  • 観測ログから人間が再現できる粒度に整っているか
シェア

お読みいただきありがとうございます

Antigravity Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

アプリ開発2026-06-13
TanStack Query v5 のキャッシュ整合性を壊さない設計 — 無効化境界・楽観的更新・SSR の落とし穴を Antigravity で詰めていく
いいねを押すと表示が一瞬で巻き戻る、別タブから戻ると古い値が残る。TanStack Query v5 で起きるキャッシュ不整合を「壊れる3地点」から逆算し、無効化境界・onMutateのロールバック・QueryClientのリクエスト分離を実コードで設計します。
アプリ開発2026-03-26
Antigravity で Chrome 拡張機能を開発する:AI 支援のブラウザツール制作ガイド
Google Antigravity IDEを使用してChrome拡張機能を開発する方法を学びます。AI支援による高速開発、Manifest V3対応、デバッグ・テスト・公開までの完全ガイド。
アプリ開発2026-03-25
Antigravity + tRPC で型安全なフルスタックAPI開発 — エンドツーエンドの型推論で開発効率を劇的に向上させる
Antigravity の AI エージェントと tRPC を組み合わせて、サーバーからクライアントまで完全に型安全な API を構築する方法を解説します。Zod バリデーション、React Query 統合、エラーハンドリングまで実践的に学べます。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →