0

Mình đã build một boilerplate Next.js 16 tích hợp AI agent thế nào

TL;DR: Một starter cho web app hiện đại: Next.js 16 (App Router + Turbopack), React 19, Supabase Auth/DB, Tailwind v4 với design tokens, i18n EN+VI, login 2-step chuẩn Apple, atoms/molecules/organisms tách lớp rõ ràng. Điểm khác: AI-first — toàn bộ tài liệu được viết để Claude Code / Cursor / Copilot đọc và làm theo. Mục tiêu: clone về, đăng nhập Supabase, gõ feature là chạy được — không tốn 2 ngày setup boilerplate.

Bài này dài. Mình chia thành các phần để bạn nhảy đến phần cần:

  1. Tại sao lại thêm một boilerplate nữa?
  2. Tech stack & lý do chọn từng cái
  3. Cấu trúc dự án — atoms / molecules / organisms
  4. AI-first: AGENTS.md, docs/, auto-memory
  5. Design system với getdesign + Tailwind v4 @theme
  6. Login chuẩn Apple: 2-step + email verify + middleware proxy
  7. i18n EN + VI auto-detect từ Accept-Language
  8. Database & migrations + RLS
  9. run.sh — single entry point cho mọi task
  10. Setup trong 5 phút
  11. Bài học sau nhiều năm

1. Tại sao lại thêm một boilerplate nữa?

Mỗi lần start project mới mình lặp lại y nguyên:

  • Pin Next.js version, set port khác 3000 (vì 3000 luôn busy)
  • Setup ESLint, Tailwind, postcss
  • Setup Supabase client (browser / server / middleware)
  • Viết middleware refresh session + protect route
  • Viết login + signup (mà thường UI cẩu thả vì "có cái form là được")
  • Tạo todos table với RLS, viết migration
  • Set Site URL + Redirect URLs trên Supabase Dashboard
  • i18n: hoặc bỏ qua, hoặc cài next-intl xong cấu hình 1 buổi sáng
  • README sơ sài → 2 tuần sau quên cách chạy
  • 6 tháng sau onboard người mới → onboarding 3 ngày

Boilerplate này gói tất cả lại. Khác biệt lớn nhất: toàn bộ tài liệu được viết để AI agent đọc và làm theo. Khi bạn mở Claude Code / Cursor lên, AI biết:

  • Đây là Next.js 16 (không phải 13/14 — APIs đã đổi)
  • File phải đặt ở đâu, atoms/molecules/organisms như thế nào
  • Đọc node_modules/next/dist/docs/01-app/ trước khi code (training data có thể stale)
  • Không bypass commit hook, eslint, typecheck
  • Khi cần new design token, edit ở đâu, sync với Tailwind ra sao

Kết quả: AI làm việc đúng convention từ prompt đầu tiên thay vì phải "huấn luyện" mỗi session.


2. Tech stack & lý do chọn từng cái

Phần Tech Lý do
Framework Next.js 16.2.6 (App Router + Turbopack) App Router ổn định, Turbopack build nhanh, Server Actions cho auth/mutation cực sạch
UI React 19.2.4 useActionState, async params / searchParams — clean code không cần thư viện ngoài
Language TypeScript 5 strict Bắt sớm lỗi runtime, autocomplete tốt hơn cho design tokens
Styling Tailwind CSS 4 Config trong CSS qua @theme, không còn tailwind.config.js. Pair với design tokens cực ngọt
Backend Supabase (Auth + Postgres + RLS) Free tier đủ dùng, RLS native, JS client tốt, không cần dựng API server riêng
Auth @supabase/ssr 0.10+ Async createClient, cookie-based session, middleware refresh
Package manager Yarn yarn.lock ổn định cross-machine; không pha trộn npm/pnpm
Lint ESLint 9 flat config + eslint-config-next Đúng chuẩn Next 16

Mình không add thêm các package "tiện" như clsx, class-variance-authority, next-intl — vì:

  • clsx: với JSX nhỏ, template string ${a} ${b} đủ dùng
  • cva: variant pattern viết tay bằng Record<Variant, string> rõ ràng hơn, không che giấu
  • next-intl: với ~30 strings, custom dictionary 60 dòng đủ; tránh phụ thuộc 50KB+

Triết lý: ưu tiên đơn giản, hiểu được toàn bộ code, ít magic.


3. Cấu trúc dự án — atoms / molecules / organisms

Phỏng theo Atomic Design (Brad Frost) — đã dùng đi dùng lại nhiều năm ở các dự án thực tế:

ad-manager/
├── app/                       # Next.js App Router
│   ├── login/                 # /login route + Server Actions
│   │   ├── actions.ts         # signIn / signUp / signOut
│   │   └── page.tsx
│   ├── signup/                # /signup route
│   ├── auth/callback/         # Supabase email-confirmation callback
│   │   └── route.ts
│   ├── layout.tsx
│   ├── page.tsx               # / (home, protected)
│   └── globals.css            # Tailwind v4 @theme tokens
├── components/                # UI building blocks
│   ├── atoms/                 # Button, Input, Label, ErrorMessage, TextLink, LocaleSwitcher
│   ├── molecules/             # FormField (Label + Input + ErrorMessage)
│   ├── organisms/             # LoginForm, SignUpForm, SignOutButton
│   └── templates/             # AuthLayout (centered auth page wrapper)
├── lib/i18n/                  # Cusom i18n (en + vi)
│   ├── dictionaries.ts
│   ├── server.ts              # getLocale (cookie + Accept-Language), getMessages, t
│   ├── client.tsx             # IntlProvider + useT
│   └── actions.ts             # setLocale Server Action
├── utils/supabase/            # Supabase client helpers
│   ├── client.ts              # createBrowserClient
│   ├── server.ts              # createClient (async)
│   └── middleware.ts          # updateSession + route protection
├── proxy.ts                   # Next 16 renamed middleware → proxy
├── supabase/
│   ├── migrations/            # SQL migrations
│   └── seed.sql
├── docs/                      # Tài liệu cho AI agent / contributor
├── DESIGN.md                  # Design tokens (managed by `getdesign` CLI)
├── AGENTS.md                  # AI agent rules entry point
├── CLAUDE.md                  # → @AGENTS.md
├── README.md                  # Setup hướng dẫn người mới
├── RUN.md                     # Hướng dẫn run.sh
└── run.sh                     # Single entry point

Quy tắc atoms / molecules / organisms:

Layer Mục đích Ví dụ
atoms Primitive nhỏ nhất, không compose component khác. Không business logic. Button, Input, Label
molecules Combine vài atoms. Tái sử dụng được. FormField = Label + Input + ErrorMessage
organisms Block đặc thù feature. Có thể wire Server Action, hold local state. LoginForm, SignUpForm
templates Layout wrapper dùng nhiều page. AuthLayout
pages Route-level (app/<segment>/page.tsx). Mỏng — orchestrate organisms. app/login/page.tsx

Hướng phụ thuộc một chiều: atom → molecule → organism → page. Atom không import molecule, molecule không import organism, v.v. Quy tắc này tránh circular dependency và làm cấu trúc thư mục "nói chuyện" thay cho code.


4. AI-first: AGENTS.md, docs/, auto-memory

Phần khác biệt nhất của boilerplate.

4.1 File entry point cho AI

CLAUDE.md            → @AGENTS.md         # Claude Code đọc đầu tiên
AGENTS.md            → cross-tool         # Cursor / Copilot / Codex cũng đọc

AGENTS.md mở đầu bằng cảnh báo:

<!-- BEGIN:nextjs-agent-rules -->
# This is NOT the Next.js you know

This version has breaking changes — APIs, conventions, and file structure
may all differ from your training data. Read the relevant guide in
`node_modules/next/dist/docs/` before writing any code.
Heed deprecation notices.
<!-- END:nextjs-agent-rules -->

Vì sao quan trọng? Next.js 16 đổi tên middleware.tsproxy.ts, params/searchParams thành async, một số API bị xóa. Training data của AI thường chưa cập nhật. Bắt AI đọc docs bundle trong node_modules/next/dist/docs/01-app/ trước khi code = đụng đúng API hiện hành.

4.2 docs/ structure mirror acm-web

docs/
├── README.md                              # Doc index + required reading order
├── ai-agent-guidelines.md                 # Do/Don't
├── operation.md                           # No-bypass rules (commit-hook, eslint, type-check, release)
├── project-overview.md
├── technology.md
└── core-principles-and-coding-standards/
    ├── structure.md                       # Component hierarchy + folder map
    ├── coding-conventions.md              # Routing, components, fetching, i18n, forms
    ├── coding-style.md                    # yarn, eslint, commands
    └── instructions-and-work-flows/
        └── adding-a-new-page.md           # Step-by-step workflow

docs/README.md định nghĩa required reading order — bắt buộc AI đọc tuần tự trước khi đề xuất giải pháp:

1. Start  → docs/README.md, project-overview.md
2. Guidelines → ai-agent-guidelines.md, AGENTS.md
3. Structure & tech → technology.md, structure.md
4. Patterns → coding-conventions.md, coding-style.md, DESIGN.md
5. Framework docs → node_modules/next/dist/docs/01-app/<relevant page>
6. Existing code → similar files under app/

4.3 No-bypass rules (operation.md)

Rule Nghĩa
no-bypass-commit-hook Không git commit --no-verify
no-bypass-eslint Fix lỗi thay vì disable rule
no-bypass-type-check yarn build phải pass; không any / @ts-ignore lách
no-auto-release Không tự deploy/publish

AI đọc xong sẽ refuse "fix nhanh" bằng cách bypass — buộc fix gốc.

4.4 Auto-memory cho Claude Code

Claude Code có hệ thống auto-memory (~/.claude/projects/<project>/memory/). Boilerplate này tận dụng để lưu:

  • Feedback (vd: "không tạo skill nếu AGENTS.md đã enforce rule")
  • Project basics (port 3002, docs layout, getdesign CLI behavior)
  • Reference (external CLIs, dashboards, knowledge gaps)

Các session sau Claude tự load → không cần re-explain context.

4.5 Triết lý "no redundant skills"

Bài học đắt giá: ban đầu mình tạo project-level skill nextjs trong .claude/skills/. Người dùng phản hồi: "AGENTS.md đã có rule này rồi, skill này thừa". Đúng — AGENTS.md được load mọi session, skill là thêm context bị overlap. Rút ra rule: nếu AGENTS.md / docs đã enforce, không wrap thành skill.


5. Design system với getdesign + Tailwind v4 @theme

5.1 DESIGN.md quản lý bởi getdesign CLI

npx getdesign@latest add apple

Tool tạo DESIGN.md ở project root với:

  • Color tokens (Action Blue, Ink, Canvas, surface tile, …)
  • Typography (hero-display 56px / 600 / -0.28px, body 17px / 400, …)
  • Spacing scale (xxs 4 → section 80)
  • Border radius (xs 5 → pill 9999)
  • Component spec (button-primary, product-tile, …)
  • Do's & Don'ts

5.2 Sync sang Tailwind v4 @theme

Tailwind v4 không còn tailwind.config.js. Config nằm trong CSS:

/* app/globals.css */
@import "tailwindcss";

@theme {
  /* Brand */
  --color-primary: #0066cc;
  --color-primary-focus: #0071e3;

  /* Surfaces */
  --color-canvas: #ffffff;
  --color-canvas-parchment: #f5f5f7;
  --color-ink: #1d1d1f;

  /* Font families */
  --font-display: "SF Pro Display", system-ui, -apple-system, "Inter", sans-serif;
  --font-text: "SF Pro Text", system-ui, -apple-system, "Inter", sans-serif;

  /* Spacing tokens */
  --spacing-xxs: 4px;
  --spacing-section: 80px;

  /* Border radius */
  --radius-md: 11px;
  --radius-lg: 18px;
  --radius-pill: 9999px;

  /* Typography composite — sets size + line-height + tracking + weight + family */
  --text-hero-display: 56px;
  --text-hero-display--line-height: 1.07;
  --text-hero-display--letter-spacing: -0.28px;
  --text-hero-display--font-weight: 600;
  --text-hero-display--font-family: var(--font-display);

  /* Product shadow — exactly ONE drop-shadow per Apple convention */
  --shadow-product: 3px 5px 30px 0 rgba(0, 0, 0, 0.22);
}

Tailwind v4 tự sinh utility cho mỗi token:

DESIGN.md key Tailwind utility
colors.primary bg-primary, text-primary
spacing.section p-section, gap-section
rounded.md rounded-md
typography.hero-display text-hero-display (1 class set hết size + weight + line-height + tracking + family)
Product shadow shadow-product

Code component cuối:

<button className="bg-primary text-on-primary text-body rounded-pill px-[22px] py-[11px] active:scale-95">
  Buy
</button>

5.3 Button variants tách lớp

Apple dùng nhiều radius khác nhau — boilerplate có 5 variants atom Button:

type Variant = "primary" | "primary-rect" | "secondary" | "dark-utility" | "pearl";
variant radius text use case
primary pill body 17px Marketing CTAs ("Buy", "Learn more")
primary-rect md (11px) button-large 18px Auth surfaces (Continue / Sign in)
secondary pill body 17px Ghost outlined primary
dark-utility md (11px) utility 14px Sign Out, nav actions
pearl md (11px) caption 14px Card secondary action

6. Login chuẩn Apple: 2-step + email verify + middleware proxy

6.1 Two-step UX

Apple sign-in nổi tiếng vì chia thành 2 step: email trước, password sau. Boilerplate copy y nguyên pattern này.

// components/organisms/LoginForm.tsx (rút gọn)
"use client";

export function LoginForm({ next }: { next?: string }) {
  const [state, formAction, pending] = useActionState(signIn, {});
  const [step, setStep] = useState<"email" | "password">("email");
  const [email, setEmail] = useState("");

  const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
    if (step === "email") {
      e.preventDefault();
      if (isValidEmail(email)) setStep("password");
    }
    // step === "password" → Server Action run natively
  };

  return (
    <form action={formAction} onSubmit={handleSubmit}>
      {step === "email" ? (
        <>
          <FormField name="email" type="email" value={email} onChange={...} />
          <Button type="submit" variant="primary-rect" disabled={!isValidEmail(email)}>
            Continue
          </Button>
        </>
      ) : (
        <>
          <input type="hidden" name="email" value={email} />
          <div>
            <span>{email}</span>
            <button type="button" onClick={() => setStep("email")}>Edit</button>
          </div>
          <FormField name="password" type="password" />
          <Button type="submit" variant="primary-rect" disabled={pending}>
            {pending ? "Signing in…" : "Sign in"}
          </Button>
        </>
      )}
    </form>
  );
}

Một <form> duy nhất chia sẻ giữa 2 step → useActionState quản lý error/pending sạch. Step 1 onSubmit intercept → flip state. Step 2 không intercept → Server Action chạy. Enter key works tự nhiên ở cả 2 step.

6.2 Server Action signIn / signUp

// app/login/actions.ts
"use server";

export async function signIn(_prev: SignInState, formData: FormData): Promise<SignInState> {
  const email = String(formData.get("email") ?? "").trim();
  const password = String(formData.get("password") ?? "");

  if (!email || !password) {
    return { error: await tServer("auth.error_required") };
  }

  const supabase = await createClient();
  const { error } = await supabase.auth.signInWithPassword({ email, password });
  if (error) return { error: error.message };

  redirect("/");
}

export async function signUp(_prev: SignUpState, formData: FormData): Promise<SignUpState> {
  // … validate
  const origin = await siteOrigin();
  const { data, error } = await supabase.auth.signUp({
    email,
    password,
    options: {
      emailRedirectTo: `${origin}/auth/callback`,  // QUAN TRỌNG
    },
  });
  // …
}

6.3 Email verify callback

Khi user click link confirm trong email, Supabase redirect về /auth/callback?code=.... Boilerplate có route handler exchange code → session:

// app/auth/callback/route.ts
export async function GET(request: Request) {
  const { searchParams, origin } = new URL(request.url);
  const code = searchParams.get("code");
  if (!code) return NextResponse.redirect(`${origin}/login?error=missing_code`);

  const supabase = await createClient();
  const { error } = await supabase.auth.exchangeCodeForSession(code);
  if (error) return NextResponse.redirect(`${origin}/login?error=${encodeURIComponent(error.message)}`);

  return NextResponse.redirect(`${origin}/`);
}

Gotcha: Supabase Dashboard → Authentication → URL Configuration mặc định Site URL = http://localhost:3000. Bắt buộc đổi sang http://localhost:3002 (port của boilerplate) và thêm http://localhost:3002/auth/callback vào Redirect URLs allow list. Không là link email vẫn trỏ về port 3000 (sai).

6.4 Middleware (Next 16 gọi là Proxy) refresh session

Next.js 16 đổi tên middleware.tsproxy.ts. Function proxy():

// proxy.ts
import { updateSession } from "@/utils/supabase/middleware";

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

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

updateSession làm 3 việc:

  1. Tạo Supabase client với cookie adapter
  2. Gọi supabase.auth.getUser() — bắt buộc để refresh expired access token
  3. Redirect logic: chưa login + path không public → /login?next=<original>; đã login + đang ở /login hoặc /signup/

7. i18n EN + VI auto-detect từ Accept-Language

Không cài next-intl (overkill cho ~30 strings). Cusom lib trong lib/i18n/:

// lib/i18n/dictionaries.ts
const en = {
  "login.title": "Management",
  "login.subtitle": "Sign in to your ad-manager account",
  "login.continue": "Continue",
  // …
};
const vi: Record<keyof typeof en, string> = {
  "login.title": "Quản lý",
  "login.subtitle": "Đăng nhập vào tài khoản ad-manager của bạn",
  "login.continue": "Tiếp tục",
  // …
};

export const dictionaries = { en, vi };

TypeScript guard: nếu vi thiếu key → compile fail. Không có cách nào ship locale thiếu key.

getLocale() resolve theo thứ tự:

export async function getLocale(): Promise<Locale> {
  const cookieStore = await cookies();
  const cookieValue = cookieStore.get("locale")?.value;
  if (isLocale(cookieValue)) return cookieValue;

  // Fallback: parse Accept-Language
  const h = await headers();
  const accept = h.get("accept-language") ?? "";
  for (const lang of accept.toLowerCase().split(",")) {
    if (lang.trim().startsWith("vi")) return "vi";
    if (lang.trim().startsWith("en")) return "en";
  }
  return "en";
}

Server Components dùng t(messages, key); Client Components wrap trong <IntlProvider> rồi gọi useT(). LocaleSwitcher cố định góc dưới phải, click → setLocale Server Action set cookie + revalidatePath("/", "layout") để re-render toàn bộ với locale mới.


8. Database & migrations + RLS

8.1 Migration đầu tiên: bảng todos chuẩn RLS

-- supabase/migrations/0001_create_todos.sql
create table public.todos (
  id          uuid primary key default gen_random_uuid(),
  user_id     uuid not null references auth.users(id) on delete cascade,
  name        text not null,
  created_at  timestamptz not null default now()
);

create index todos_user_id_idx on public.todos(user_id);

alter table public.todos enable row level security;

create policy "Users can read own todos"
  on public.todos for select
  to authenticated
  using ((select auth.uid()) = user_id);

create policy "Users can insert own todos"
  on public.todos for insert
  to authenticated
  with check ((select auth.uid()) = user_id);

create policy "Users can update own todos"
  on public.todos for update
  to authenticated
  using ((select auth.uid()) = user_id)
  with check ((select auth.uid()) = user_id);  -- chống đổi user_id sang user khác

create policy "Users can delete own todos"
  on public.todos for delete
  to authenticated
  using ((select auth.uid()) = user_id);

Pattern owner-only chuẩn Supabase:

  • Enable RLS trên mọi table trong public
  • Policy split theo từng operation (select / insert / update / delete)
  • UPDATE phải có cả USINGWITH CHECK (không thì user có thể reassign user_id sang user khác)
  • to authenticated (không auth.role() = 'authenticated' — deprecated)

8.2 Apply migration

bash run.sh login                       # 1 lần / máy
bash run.sh link <project-ref>          # 1 lần / repo
bash run.sh migrate                     # áp dụng pending migrations

9. run.sh — single entry point cho mọi task

bash run.sh <command>

App lifecycle:
  start       yarn dev
  build       yarn build
  prod        build + start
  lint        eslint
  typecheck   tsc --noEmit
  verify      lint + typecheck

Database (Supabase):
  login       supabase login (1 lần / máy)
  link REF    supabase link --project-ref REF (1 lần / repo)
  migrate     supabase db push
  migration:new NAME
  seed        Apply supabase/seed.sql (psql nếu DATABASE_URL có, else Dashboard instructions)

Deployment:
  deploy      Placeholder (configure cho Vercel / Netlify / Docker)

  help

Lợi:

  • 1 cách invoke cho mọi task, không cần nhớ yarn build vs supabase db push vs psql -f
  • CI / docs / onboarding đều reference cùng một command
  • Logic phức tạp (seed auto-detect DATABASE_URL, link refuse nếu chưa login) gói gọn trong shell function

10. Setup trong 5 phút

# 1. Clone
git clone <repo>
cd ad-manager

# 2. Install
yarn install

# 3. Tạo .env.local
cat > .env.local <<EOF
NEXT_PUBLIC_SUPABASE_URL=https://<your-ref>.supabase.co
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=<your-anon-key>
NEXT_PUBLIC_SITE_URL=http://localhost:3002
EOF

# 4. Link + migrate
bash run.sh login                       # opens browser, generate token
bash run.sh link <your-project-ref>
bash run.sh migrate

# 5. Supabase Dashboard cần config:
#    Authentication → URL Configuration:
#      Site URL: http://localhost:3002
#      Redirect URLs: http://localhost:3002/auth/callback

# 6. Tạo test user (Dashboard → Auth → Users → Add user, bật Auto Confirm)

# 7. Chạy
bash run.sh start
# Mở http://localhost:3002

11. Bài học sau nhiều năm

Một số nguyên tắc mình rút ra qua nhiều dự án — đều cài cứng vào boilerplate này:

11.1 Đừng fight với convention của tool

getdesign ghi DESIGN.md ở project root. Mình từng cố move vào docs/design.md cho gọn — lần update tool đè lại ở root, break references. Theo tool, đừng cãi. Boilerplate document hành vi này trong auto-memory để session AI sau không lặp.

11.2 AGENTS.md / docs/ > Skills

Skill nghe có vẻ chuyên nghiệp, nhưng AGENTS.md được load mặc định mỗi session, skill thì on-demand. Convention thuộc về docs/ để tự động phân phối; skill chỉ dành cho on-demand content (vd: HIG checklist khi review UI). Đừng wrap convention thành skill — overlap = noise.

11.3 DESIGN.md là single source of truth

Mọi token (color, spacing, typography, radius) bắt đầu ở DESIGN.md, sync sang globals.css (Tailwind v4 @theme). Không inline hex, không custom CSS class một-lần-dùng. Sự đồng nhất tại 1 chỗ → toàn app đồng nhất.

11.4 Bỏ tay khỏi next.config.ts cho mấy thứ không thuộc về nó

Port không phải config Next.js — không có option cho nó. Port set qua CLI flag (-p 3002) hoặc env PORT. Mỗi lần thấy AI add port vào next.config.ts mình biết training data đã stale → AGENTS.md đã ghi rõ điều này.

11.5 Two-step login UX > one-step

Apple-style chia email + password vào 2 step → giảm cảm giác "form dài", tăng conversion. Cost: 30 dòng React state. Đáng.

11.6 RLS từ ngày đầu, không phải sau

Mọi table public enable RLS ngay từ migration đầu tiên. Quên một bảng = data exposed qua REST API. Mặc định "deny everything" + add policy explicit > mặc định "allow" rồi siết sau.

11.7 Email verify cần URL Configuration đúng

99% lỗi "click link email không vào được app" đều do Site URL / Redirect URLs trên Supabase Dashboard sai. Document chỉ rõ trong README + giải thích tại sao trong bài này — đỡ tốn giờ debug.

11.8 i18n thì làm sớm

Bookmarking để "add sau" gần như không bao giờ xảy ra. 30 strings × t() lookup không tốn gì, tránh refactor sau khi có 500 strings hardcoded.

11.9 README + RUN.md cho người mới

6 tháng sau bạn cũng là người mới. README ngắn gọn cách setup. RUN.md sâu hơn về run.sh. AGENTS.md cho AI. docs/ cho contributor. Mỗi loại reader có entry point của họ.

11.10 No bypass

Bypass commit hook / eslint / typecheck = nợ kỹ thuật ngay tại commit đó. Fix tận gốc luôn — boilerplate document explicit rule (operation.md), AI bắt buộc tuân theo.


Kết luận

Mục tiêu của boilerplate này không phải "framework mới" — mà là kết tinh practice đã chạy production qua nhiều dự án thực, cộng thêm lớp tài liệu để AI agent làm việc đúng convention từ prompt đầu tiên.

Bạn clone về, làm theo 7 bước setup ở trên, là có:

  • Login / signup chuẩn UX
  • Email verify hoạt động
  • DB với RLS chuẩn
  • i18n EN + VI
  • Design tokens trong Tailwind
  • AI hiểu codebase, làm việc đúng convention

Phần feature business của app — bạn code. Phần boilerplate — đã xong.

Repo: https://github.com/dinhuty/nextjs-16-boilerplate-full-agents-design-apple

Câu hỏi / góp ý → comment dưới bài. Cảm ơn đã đọc đến cuối 🙏


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.