ProductCard.jsx
, useAuth.js
).<mark>“Làm thế nào để cấu trúc này vẫn hợp lý khi dự án mở rộng?”</mark>
.Loại Component | Mục đích | Ví dụ |
---|---|---|
UI Components (components/ui) | Các element nhỏ, có thể tái sử dụng (Button, Input) | Button, Select, Toggle |
Layout Components (components/layout) | Các thành phần cấu trúc trang (Header, Footer, Navbar) | Header, Footer, Navbar |
Page-Specific Components (components/pages) | Dành riêng cho từng trang, không dùng lại đa nơi | HomePageHero, ProductDetailsCard |
Common Reusable Components (components/common) | Component chung dùng khắp nơi (Modal, Notification) | Modal, Loader, Tooltip |
// components/ui/Button.jsxexport default function Button({ children, variant, ...props }) { return <button className={`btn ${variant}`} {...props}>{children}</button>;}
<Button variant="primary">Save</Button><Button variant="secondary">Cancel</Button><Button variant="danger">Delete</Button>
src/app/api/├── products/│ ├── route.js # Xử lý `/api/products`│ └── [id]/route.js # Xử lý `/api/products/{id}`├── orders/│ ├── route.js│ └── [id]/route.js└── users/ ├── route.js └── [id]/route.js
// lib/sendResponse.jsexport function sendResponse(status, data = null, message = '') { return Response.json({ status, data, message }, { status });}
import { z } from 'zod';export const productSchema = z.object({ name: z.string().min(1, "Name is required"), price: z.number().positive(), description: z.string().optional(),});
// Trong route.jsconst body = await req.json();const result = productSchema.safeParse(body);if (!result.success) { return sendResponse(400, null, result.error.format());}
// middleware/auth.jsexport async function authMiddleware(req) { const token = req.headers.get('Authorization'); if (!token) throw new Error('Unauthorized'); // Logic kiểm tra token}
try { const product = await getProductById(params.id); if (!product) return sendResponse(404, null, "Product not found."); return sendResponse(200, product);} catch (error) { return sendResponse(500, null, "Internal server error.");}
Thư mục | Chức năng | Ví dụ |
---|---|---|
utils/ | Hàm tiện ích nhỏ, không phụ thuộc nhiều vào logic nghiệp vụ | formatDate.js , capitalize.js |
lib/ | Logic nghiệp vụ/phức tạp hơn, kết nối API, xác thực, DB | apiClient.js , auth.js , db.js |
// utils/formatDate.jsexport function formatDate(dateString, locale = 'en-US') { const date = new Date(dateString); return new Intl.DateTimeFormat(locale, { year: 'numeric', month: 'long', day: 'numeric' }).format(date);}
import { formatDate } from '@/utils/formatDate';
<span>{formatDate(order.createdAt)}</span>
// lib/apiClient.jsexport async function fetcher(endpoint, options = {}) { const res = await fetch(`${process.env.API_URL}${endpoint}`, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${process.env.API_KEY}`, }, ...options, }); if (!res.ok) throw new Error(`API error: ${res.statusText}`); return res.json();}
import { fetcher } from '@/lib/apiClient';const products = await fetcher('/products');
import dynamic from 'next/dynamic';
const DynamicChart = dynamic(() => import('@/components/ui/Chart'), { loading: () => <p>Loading chart...</p>, ssr: false,});
export default function DashboardPage() { return ( <div> <h2>Your Stats</h2> <DynamicChart /> </div> );}
// stores/authStore.jsimport { create } from 'zustand';
export const useAuthStore = create((set) => ({ user: null, setUser: (user) => set({ user }), logout: () => set({ user: null }),}));
// app/products/page.jsexport const revalidate = 60;
export default async function ProductsPage() { const products = await fetchProducts(); return <ProductList products={products} />;}
// middleware.jsexport async function middleware(req) { const { pathname } = req.nextUrl; if (pathname.startsWith('/dashboard')) { const token = req.cookies.get('token'); if (!token) { return Response.redirect(new URL('/login', req.url)); } }}