Loading the catalog…
Loading the catalog…
Next.js App Router로 대시보드를 만들면서 꼭 짚고 넘어가야 했던 개념들을 정리했다. 라우팅 → 레이아웃 → 내비게이션 → 배포/DB → 환경변수 순서로 흐름을 따라간다. 1. 폴더 기반 라우팅과 Route Segment, page.tsx 의 역할 App Router에서는 app 폴더 안의 폴더 구조가 곧 URL 구조 다. 라우터 설정 파일을 따로 만들 필요 없이 폴더를 만들면 경로가 생긴다. app/ ├── page.tsx → / └── dashboard/ ├── page.tsx → /dashboard ├── customers/ │ └── page.tsx → /dashboard/customers └── invoices/ └── page.tsx → /dashboard/invoices Route Segment : URL을 / 로 나눈 각 조각( dashboard , customers )이 하나의 폴더와 1:1로 대응한다. page.tsx : 해당 세그먼트를 실제로 접근 가능한 페이지로 만드는 특별한 파일 이다. 폴더만 있고 page.tsx 가 없으면 그 경로는 404가 된다. // app/dashboard/page.tsx export default function Page() { return <p>Dashboard Page</p>; } 핵심은 "폴더 = 경로, page.tsx = 공개 여부" 라는 것. 2. Root Layout, 중첩 layout.tsx , Colocation, Partial Rendering Root Layout app/layout.tsx 는 모든 페이지를 감싸는 최상위 레이아웃 이고, App Router에서 필수 다. <html> 과 <body> 태그를 반드시 여기서 정의해야 한다. 전역 폰트, 전역 CSS, 메타데이터도 보통 여기에 둔다. // app/layout.tsx import '@/app/ui/global.css'; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang="ko"> <body>{children}</body> </html> ); } 중첩 레이아웃 특정 구간에만 적용할 레이아웃은 그 폴더 안에 layout.tsx 를 두면 된다. // app/dashboard/layout.tsx import SideNav from '@/app/ui/dashboard/sidenav'; export default function Layout({ children }: { children: React.ReactNode }) { return ( <div className="flex h-screen"> <div className="w-64"><SideNav /></div> <div className="flex-grow p-6">{children}</div> </div> ); } /dashboard , /dashboard/customers , /dashboard/invoices 모두 이 사이드바 레이아웃 안에서 렌더링된다. 레이아웃은 바깥에서 안쪽으로 중첩 된다: RootLayout > DashboardLayout > Page Colocation page.tsx (와 route.ts )만 라우트로 공개되기 때문에, 같은 폴더에 컴포넌트·스타일·테스트·유틸 파일을 함께 둬도 URL로 노출되지 않는다. 관련 파일을 기능 단위로 가까이 모아둘 수 있다는 게 Colocation의 장점이다. app/dashboard/invoices/ ├── page.tsx ← 라우트로 공개 ├── invoice-table.tsx ← 공개되지 않음 (같이 둬도 OK) └── utils.ts ← 공개되지 않음 Partial Rendering 페이지를 이동할 때 바뀐 부분(page)만 다시 렌더링하고, 공유하는 레이아웃은 그대로 둔다. /dashboard/customers → /dashboard/invoices 로 이동해도 DashboardLayout 은 다시 그려지지 않는다. 3. 레이아웃이 페이지 이동 중 공유 UI와 상태를 유지하는 방식 Partial Rendering 덕분에 레이아웃 컴포넌트는 언마운트되지 않는다. 그래서: 사이드바, 헤더 같은 공유 UI가 깜빡이지 않고 그대로 남아있고 레이아웃 안의 React 상태( useState ), 스크롤 위치, 입력값 등이 유지된다. 예를 들어 사이드바에 "접기/펼치기" 상태가 있다면, 하위 페이지를 오가도 접힌 상태가 그대로 유지된다. 반대로 page.tsx 는 이동할 때마다 새로 렌더링되므로 그 안의 상태는 초기화된다. 💡 이동할 때마다 상태를 초기화하고 싶다면 layout.tsx 대신 template.tsx 를 쓰면 된다. template은 이동 시마다 새 인스턴스가 만들어진다. 4. HTML 앵커와 next/link 의 차이, Client-side Navigation과 Prefetching <a> vs <Link> // ❌ 전체 페이지 새로고침 발생 <a href="/dashboard/invoices">Invoices</a> // ✅ 클라이언트 사이드 내비게이션 import Link from 'next/link'; <Link href="/dashboard/invoices">Invoices</Link> <a> <Link> 이동 방식 서버에 새 HTML 요청 → 전체 새로고침 JS로 필요한 부분만 교체 레이아웃 상태 초기화됨 유지됨 체감 속도 느림 (화면 깜빡임) 빠름 Client-side Navigation <Link> 를 클릭하면 브라우저가 페이지를 새로 받지 않고, Next.js 라우터가 바뀐 세그먼트에 필요한 데이터만 가져와 교체 한다. 앞에서 본 Partial Rendering이 가능한 이유가 바로 이것이다. 또 Next.js는 라우트 세그먼트 단위로 코드를 자동 분할(Code Splitting) 하기 때문에, 처음 접속할 때 앱 전체 코드를 받지 않는다. 한 페이지에서 에러가 나도 다른 페이지는 영향을 덜 받는다. Prefetching 프로덕션 환경에서는 <Link> 가 화면(뷰포트)에 보이는 순간, 연결된 경로의 코드를 백그라운드에서 미리 불러온다. 그래서 사용자가 실제로 클릭할 때는 이미 준비되어 있어 거의 즉시 전환된다. ⚠️ Prefetching은 next dev (개발 모드)에서는 동작 방식이 다르고, next build && next start 환경에서 제대로 확인할 수 있다. 5. usePathname() 으로 활성 링크 표시하기 & 'use client' 가 필요한 이유 사이드바에서 현재 페이지 메뉴를 강조하려면 현재 경로 를 알아야 한다. 이때 usePathname() 훅을 쓴다. // app/ui/dashboard/nav-links.tsx 'use client'; import Link from 'next/link'; import { usePathname } from 'next/navigation'; import clsx from 'clsx'; const links = [ { name: 'Home', href: '/dashboard' }, { name: 'Invoices', href: '/dashboard/invoices' }, { name: 'Customers', href: '/dashboard/customers' }, ]; export default function NavLinks() { const pathname = usePathname(); return ( <> {links.map((link) => ( <Link key={link.name} href={link.href} className={clsx( 'flex h-12 items-center rounded-md px-3 text-sm', { 'bg-sky-100 text-blue-600': pathname === link.href }, )} > {link.name} </Link> ))} </> ); } usePathname() 은 /dashboard/invoices 같은 현재 경로 문자열을 돌려준다. clsx 로 pathname === link.href 일 때만 강조 클래스를 붙인다. 왜 'use client' 가 필요할까? App Router의 컴포넌트는 기본이 Server Component 다. 서버 컴포넌트는 서버에서 한 번 렌더링되고 끝나기 때문에 React 훅( useState , useEffect , usePathname 등)과 브라우저 API를 쓸 수 없다. usePathname() 은 브라우저에서 현재 URL을 구독하는 훅이므로, 파일 맨 위에 'use client' 를 선언해 Client Component로 전환 해야 한다. 💡 팁: 'use client' 는 필요한 컴포넌트(여기선 NavLinks )에만 좁게 붙이자. 레이아웃 전체를 클라이언트로 만들면 서버 컴포넌트의 이점(번들 크기 감소, 서버 데이터 페칭)을 잃는다. 6. GitHub · Vercel · PostgreSQL 연결 흐름과 Seed 데이터의 역할 전체 흐름 로컬 코드 ──push──▶ GitHub ──자동 배포──▶ Vercel ◀──연결──▶ PostgreSQL GitHub에 저장소 생성 후 push Vercel에서 GitHub 저장소 Import → 프레임워크(Next.js)를 자동 인식해 빌드·배포 이후 main 브랜치에 push할 때마다 자동 재배포, PR마다 Preview 배포가 생성된다. Vercel 프로젝트의 Storage 탭에서 Postgres DB 생성 후 프로젝트에 연결 (현재는 Vercel Marketplace를 통해 Neon 같은 Postgres 제공자를 연결하는 방식) 연결하면 POSTGRES_URL 등 접속 정보가 Vercel 환경변수로 자동 등록 된다. 로컬로 환경변수 가져오기 : 대시보드에서 값을 복사해 .env 에 넣거나 vercel env pull 사용 Seed 데이터의 역할 DB를 막 만들면 테이블도, 데이터도 없는 빈 상태 다. Seed는 개발에 필요한 테이블 생성 + 초기 데이터 삽입 을 한 번에 해주는 스크립트다. // app/seed/route.ts (요약) import bcrypt from 'bcrypt'; import postgres from 'postgres'; import { users } from '../lib/placeholder-data'; const sql = postgres(process.env.POSTGRES_URL!, { ssl: 'require' }); async function seedUsers() { await sql`CREATE TABLE IF NOT EXISTS users ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, name VARCHAR(255) NOT NULL, email TEXT NOT NULL UNIQUE, password TEXT NOT NULL );`; await Promise.all( users.map(async (user) => { const hashed = await bcrypt.hash(user.password, 10); return sql`INSERT INTO users (id, name, email, password) VALUES (${user.id}, ${user.name}, ${user.email}, ${hashed}) ON CONFLICT (id) DO NOTHING;`; }), ); } export async function GET() { await seedUsers(); return Response.json({ message: 'Database seeded successfully' }); } 로컬 서버를 띄우고 localhost:3000/seed 에 접속하면 실행된다. Seed를 쓰는 이유: 재현 가능성 : 팀원 누구나 같은 초기 데이터로 개발 시작 UI 개발 가능 : 데이터가 있어야 테이블·차트 화면을 만들 수 있다 안전한 재실행 : IF NOT EXISTS , ON CONFLICT DO NOTHING 으로 여러 번 실행해도 중복이 생기지 않는다 비밀번호는 평문이 아니라 bcrypt 해시로 저장 한다 ⚠️ 시드가 끝나면 seed 라우트는 삭제하거나 막아두는 게 좋다. 배포 환경에 남아 있으면 누구나 URL로 실행할 수 있다. 7. .env.local 과 배포 환경변수의 역할, Secret 관리 원칙 .env.local 내 컴퓨터에서만 쓰는 환경변수 파일. Next.js가 자동으로 읽어 process.env 에 넣어준다. DB 접속 주소, API 키, 인증 Secret 등 코드에 하드코딩하면 안 되는 값 을 넣는다. # .env.local POSTGRES_URL="postgres://user:password@host/db" AUTH_SECRET="랜덤하게_생성한_긴_문자열" NEXT_PUBLIC_SITE_URL="http://localhost:3000" 배포 환경변수 배포 서버에는 .env.local 이 올라가지 않는다(올라가면 안 된다). 대신 Vercel 대시보드 → Settings → Environment Variables 에 등록한다. Production / Preview / Development 환경별로 다른 값을 줄 수 있다. 환경변수를 바꾼 뒤에는 재배포해야 반영된다. NEXT_PUBLIC_ 접두사 변수 접근 범위 POSTGRES_URL 서버에서만 접근 가능 NEXT_PUBLIC_SITE_URL 빌드 시 클라이언트 번들에 포함 → 브라우저에서 누구나 볼 수 있음 Secret 관리 원칙 .env*.local 은 반드시 .gitignore 에 추가 — 한 번 커밋되면 git 기록에 남는다. Secret에는 절대 NEXT_PUBLIC_ 을 붙이지 않는다. 협업용으로는 값이 비어 있는 .env.example 만 커밋해서 어떤 변수가 필요한지 공유한다. 로컬·Preview·Production에 서로 다른 키 를 쓴다. 실수로 노출됐다면 커밋을 지우는 것만으로는 부족하다. 즉시 키를 폐기하고 재발급(rotate) 한다. 마무리 정리하면 하나의 흐름으로 이어진다. 폴더로 경로를 만들고 ( page.tsx ), 레이아웃으로 공유 UI를 감싸고 ( layout.tsx ), <Link> 로 이동하니 레이아웃이 유지되고 (Partial Rendering + Client-side Navigation), 현재 경로를 알아야 하는 UI는 클라이언트 컴포넌트로 분리하고 ( 'use client' + usePathname ), GitHub → Vercel → Postgres로 배포·연결하고, Seed로 초기 데이터를 채우고 , 민감한 값은 환경변수로 분리해 안전하게 관리한다. App Router는 "서버가 기본, 필요한 곳만 클라이언트"라는 철학 위에 있다는 걸 의식하면 대부분의 개념이 자연스럽게 연결된다. 참고 Next.js Docs — Layouts and Pages Next.js Docs — Linking and Navigating Next.js Docs — Environment Variables Next.js Learn — Dashboard App
What RADAR observed and classified to build this opportunity. It is what the source published, not a verification that the offer is still active.
[Next.js] 공부 2주차. Next.js App Router로 대시보드를 만들면서 꼭 짚고 넘어가야 했던 개념들을 정리했다. 라우팅 → 레이아웃 → 내비게이션 → 배포/DB → 환경변수 순서로 흐름을 따라간다. 1. 폴더 기반 라우팅과 Route Segment, page.tsx 의 역할 App Router에서는 app 폴더 안의 폴더 구조가 곧 URL 구조 다. 라우터 설정 파일을 따로 만들 필요 없이 폴더를 만들면 경로가 생긴다. app/ ├── page.tsx → / └── dashboard/ ├── page.tsx → /dashboard ├── customers/ │ └── page.tsx → /dashboard/customers └── invoices/ └── page.tsx →…
Open source