Bỏ qua

Phase 1: Project Setup

Mục tiêu

Thiết lập cấu trúc dự án TaskFlow và kết nối các services cơ bản.

Thời gian ước tính: 2 giờ


Prerequisites

Accounts cần thiết

  • [ ] GitHub account
  • [ ] Supabase account (supabase.com)
  • [ ] Cloudflare account (cloudflare.com)

Tools cần cài đặt

# Node.js 18+
node --version

# pnpm (recommended) hoặc npm
pnpm --version

# Supabase CLI
npm install -g supabase

# Cloudflare Wrangler
npm install -g wrangler

# Git
git --version

Step 1: Create Next.js Project

1.1 Initialize project

# Create new Next.js project
pnpm create next-app@latest taskflow --typescript --tailwind --eslint --app --src-dir --import-alias "@/*"

cd taskflow

1.2 Install dependencies

# Supabase client
pnpm add @supabase/supabase-js @supabase/ssr

# Cloudflare adapter
pnpm add @opennextjs/cloudflare

# UI dependencies
pnpm add clsx tailwind-merge lucide-react

# Date handling
pnpm add date-fns

# Dev dependencies
pnpm add -D @types/node

1.3 Update project structure

taskflow/
├── src/
│   ├── app/
│   │   ├── (auth)/
│   │   │   ├── login/
│   │   │   │   └── page.tsx
│   │   │   └── register/
│   │   │       └── page.tsx
│   │   ├── (dashboard)/
│   │   │   ├── dashboard/
│   │   │   │   └── page.tsx
│   │   │   └── layout.tsx
│   │   ├── layout.tsx
│   │   └── page.tsx
│   ├── components/
│   │   └── ui/
│   ├── lib/
│   │   ├── supabase/
│   │   │   ├── client.ts
│   │   │   ├── server.ts
│   │   │   └── middleware.ts
│   │   └── utils.ts
│   └── types/
│       └── index.ts
├── supabase/
│   ├── config.toml
│   └── migrations/
├── public/
├── open-next.config.ts
├── wrangler.toml
└── package.json

Tạo các thư mục cần thiết:

mkdir -p src/app/{(auth)/{login,register},(dashboard)/dashboard}
mkdir -p src/{components/ui,lib/supabase,types}
mkdir -p supabase/migrations

Step 2: Setup Supabase

2.1 Create Supabase project

  1. Đăng nhập vào Supabase Dashboard
  2. Click "New Project"
  3. Điền thông tin:
  4. Organization: (chọn hoặc tạo mới)
  5. Name: taskflow-staging (hoặc taskflow-prod)
  6. Database Password: (tạo password mạnh)
  7. Region: Southeast Asia (Singapore)
  8. Click "Create new project"
  9. Đợi project được tạo (~2 phút)

2.2 Get API keys

Vào Project Settings → API:

  • SUPABASE_URL: Project URL
  • SUPABASE_ANON_KEY: anon public key
  • SUPABASE_SERVICE_ROLE_KEY: service_role key (secret!)

2.3 Initialize Supabase locally

# Login to Supabase CLI
supabase login

# Initialize Supabase in project
supabase init

# Link to remote project
supabase link --project-ref YOUR_PROJECT_REF

2.4 Create Supabase client files

// src/lib/supabase/client.ts
import { createBrowserClient } from '@supabase/ssr';

export function createClient() {
  return createBrowserClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
  );
}
// src/lib/supabase/server.ts
import { createServerClient, type CookieOptions } from '@supabase/ssr';
import { cookies } from 'next/headers';

export async function createClient() {
  const cookieStore = await cookies();

  return createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll() {
          return cookieStore.getAll();
        },
        setAll(cookiesToSet) {
          try {
            cookiesToSet.forEach(({ name, value, options }) =>
              cookieStore.set(name, value, options)
            );
          } catch {
            // Server Component
          }
        },
      },
    }
  );
}
// src/lib/supabase/middleware.ts
import { createServerClient } from '@supabase/ssr';
import { NextResponse, type NextRequest } from 'next/server';

export async function updateSession(request: NextRequest) {
  let supabaseResponse = NextResponse.next({
    request,
  });

  const supabase = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll() {
          return request.cookies.getAll();
        },
        setAll(cookiesToSet) {
          cookiesToSet.forEach(({ name, value, options }) =>
            request.cookies.set(name, value)
          );
          supabaseResponse = NextResponse.next({
            request,
          });
          cookiesToSet.forEach(({ name, value, options }) =>
            supabaseResponse.cookies.set(name, value, options)
          );
        },
      },
    }
  );

  await supabase.auth.getUser();

  return supabaseResponse;
}
// src/middleware.ts
import { type NextRequest } from 'next/server';
import { updateSession } from '@/lib/supabase/middleware';

export async function middleware(request: NextRequest) {
  return await updateSession(request);
}

export const config = {
  matcher: [
    '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
  ],
};

Step 3: Configure Environment Variables

3.1 Create .env.local

# .env.local (gitignored)
NEXT_PUBLIC_SUPABASE_URL=https://YOUR_PROJECT.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

# Server-only (for API routes, Server Actions)
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

3.2 Create .env.example

# .env.example (committed to git)
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_ANON_KEY=
SUPABASE_SERVICE_ROLE_KEY=

3.3 Update .gitignore

# .gitignore
.env
.env.local
.env.*.local
.dev.vars

# Supabase
.supabase/

# Cloudflare
.wrangler/

Step 4: Configure Cloudflare

4.1 Create OpenNext config

// open-next.config.ts
import type { OpenNextConfig } from '@opennextjs/cloudflare';

const config: OpenNextConfig = {
  default: {
    override: {
      wrapper: 'cloudflare-node',
      converter: 'edge',
    },
  },
};

export default config;

4.2 Create wrangler.toml

# wrangler.toml
name = "taskflow"
compatibility_date = "2024-01-01"
compatibility_flags = ["nodejs_compat"]
main = ".open-next/worker.js"

[vars]
APP_ENV = "development"

# Assets
[site]
bucket = ".open-next/assets"

# KV for caching (optional)
# [[kv_namespaces]]
# binding = "CACHE"
# id = "your-kv-namespace-id"

4.3 Update package.json scripts

{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "build:worker": "npx @opennextjs/cloudflare",
    "preview": "npm run build:worker && wrangler dev",
    "deploy": "npm run build:worker && wrangler deploy"
  }
}

Step 5: Create Base Layout

5.1 Update root layout

// src/app/layout.tsx
import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import './globals.css';

const inter = Inter({ subsets: ['latin'] });

export const metadata: Metadata = {
  title: 'TaskFlow',
  description: 'Task management for small teams',
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className={inter.className}>{children}</body>
    </html>
  );
}

5.2 Create home page

// src/app/page.tsx
import Link from 'next/link';

export default function HomePage() {
  return (
    <main className="flex min-h-screen flex-col items-center justify-center p-24">
      <h1 className="text-4xl font-bold mb-8">TaskFlow</h1>
      <p className="text-xl text-gray-600 mb-8">
        Task management for small teams
      </p>
      <div className="flex gap-4">
        <Link
          href="/login"
          className="bg-blue-600 text-white px-6 py-2 rounded-lg hover:bg-blue-700"
        >
          Login
        </Link>
        <Link
          href="/register"
          className="border border-blue-600 text-blue-600 px-6 py-2 rounded-lg hover:bg-blue-50"
        >
          Register
        </Link>
      </div>
    </main>
  );
}

Step 6: Test Setup

6.1 Run local development

# Start Next.js dev server
pnpm dev

# Open http://localhost:3000

6.2 Test Supabase connection

// src/app/test/page.tsx (temporary)
import { createClient } from '@/lib/supabase/server';

export default async function TestPage() {
  const supabase = await createClient();

  const { data, error } = await supabase.auth.getSession();

  return (
    <div className="p-8">
      <h1 className="text-2xl font-bold mb-4">Supabase Connection Test</h1>
      {error ? (
        <p className="text-red-600">Error: {error.message}</p>
      ) : (
        <p className="text-green-600">Connected successfully!</p>
      )}
      <pre className="mt-4 p-4 bg-gray-100 rounded">
        {JSON.stringify({ session: data.session }, null, 2)}
      </pre>
    </div>
  );
}

6.3 Test Cloudflare preview

# Build and preview with Wrangler
pnpm preview

# Open http://localhost:8787

Verification Checklist

Trước khi chuyển sang Phase 2, đảm bảo:

  • [ ] Next.js app chạy local (pnpm dev)
  • [ ] Supabase project đã tạo
  • [ ] Environment variables đã cấu hình
  • [ ] Supabase CLI đã link với project
  • [ ] Test page kết nối thành công với Supabase
  • [ ] Cloudflare preview hoạt động (pnpm preview)
  • [ ] Git repository đã khởi tạo

Common Issues

Issue: Supabase connection error

Error: Invalid API key

Solution: Kiểm tra lại NEXT_PUBLIC_SUPABASE_URL và NEXT_PUBLIC_SUPABASE_ANON_KEY trong .env.local

Issue: Wrangler build error

Error: Could not resolve "@opennextjs/cloudflare"

Solution: Chạy pnpm install lại và đảm bảo package đã được cài

Error: Cookies can only be modified in Server Actions or Route Handlers

Solution: Đảm bảo sử dụng đúng pattern của @supabase/ssr cho middleware


Next Phase

Chuyển sang Phase 2: Database Design để thiết kế database schema.