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¶
- Đăng nhập vào Supabase Dashboard
- Click "New Project"
- Điền thông tin:
- Organization: (chọn hoặc tạo mới)
- Name:
taskflow-staging(hoặctaskflow-prod) - Database Password: (tạo password mạnh)
- Region: Southeast Asia (Singapore)
- Click "Create new project"
- Đợi project được tạo (~2 phút)
2.2 Get API keys¶
Vào Project Settings → API:
SUPABASE_URL: Project URLSUPABASE_ANON_KEY: anon public keySUPABASE_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¶
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¶
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¶
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¶
Solution: Kiểm tra lại NEXT_PUBLIC_SUPABASE_URL và NEXT_PUBLIC_SUPABASE_ANON_KEY trong .env.local
Issue: Wrangler build error¶
Solution: Chạy pnpm install lại và đảm bảo package đã được cài
Issue: Cookie error in middleware¶
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.