Loading the catalog…
Loading the catalog…
환경: Next.js 16.0.10 · TypeScript · pnpm 교재: Next.js Learn 대시보드 강좌 Chapter 4: Creating Layouts and Pages Chapter 5: Navigating Between Pages Chapter 6: Setting Up Your Database 이번 주는 대시보드 페이지들을 만들고, 페이지 이동을 부드럽게 바꾸고, 실제 DB를 연결해서 배포까지 해봤다. 1. 폴더 기반 라우팅과 page.tsx Next.js(App Router)는 폴더 구조가 곧 URL 이다. 폴더 하나가 URL의 한 조각(Route Segment)이 된다. app/ ├── page.tsx → / └── dashboard/ ├── page.tsx → /dashboard ├── customers/ │ └── page.tsx → /dashboard/customers └── invoices/ └── page.tsx → /dashboard/invoices // app/dashboard/page.tsx export default function Page() { return <p>Dashboard Page</p>; } 폴더만 만들면 왜 안 될까? 폴더는 주소의 경로 만 만든다. 그 주소에서 무엇을 보여줄지 는 page.tsx 가 정한다. Next.js는 폴더 안에서 page.tsx 라는 약속된 이름의 파일 을 찾고, 그 파일이 export default 로 내보낸 컴포넌트를 화면에 그린다. 그래서 폴더만 있고 page.tsx 가 없으면 404가 뜬다. 이 규칙 덕분에 app/ui , app/lib 처럼 page.tsx 가 없는 폴더는 주소가 생기지 않는다. 컴포넌트나 유틸 코드를 라우트 근처에 함께 둬도 공개되지 않는데, 이걸 Colocation 이라고 한다. 2. 레이아웃 중첩과 Partial Rendering 대시보드의 모든 페이지에는 같은 사이드바가 필요하다. 페이지마다 복사하지 않고 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 flex-col md:flex-row md:overflow-hidden"> <div className="w-full flex-none md:w-64"> <SideNav /> </div> <div className="grow p-6 md:overflow-y-auto md:p-12">{children}</div> </div> ); } page.tsx 와 layout.tsx 가 합쳐지는 과정 {children} 은 "현재 주소의 페이지가 들어올 빈자리" 다. /dashboard/customers 에 접속하면 Next.js가 레이아웃과 페이지를 이렇게 끼워 맞춘다. RootLayout (app/layout.tsx) ← <html>, <body>, 폰트, 전역 CSS └── DashboardLayout (app/dashboard/layout.tsx) ← 사이드바 └── customers/page.tsx ← "Customers Page" (children 자리) 루트 레이아웃 ( app/layout.tsx ): 모든 페이지를 감싸는 필수 레이아웃 대시보드 레이아웃 ( app/dashboard/layout.tsx ): dashboard 폴더 아래 페이지만 감싼다 그래서 홈( / )에는 루트 레이아웃만 적용돼서 사이드바가 없고, /dashboard 로 시작하는 주소에만 사이드바가 나온다. "상속"보다는 "감싼다" 처음엔 "하위 폴더가 레이아웃을 상속받으니까 부분 렌더링이 된다"고 이해했는데, 정확히는 상위 레이아웃이 하위 페이지를 바깥에서 감싸는 구조(중첩) 다. 하위 페이지가 레이아웃 코드를 물려받는 게 아니라, 레이아웃 안의 {children} 자리에 들어가는 것이다. Partial Rendering customers → invoices로 이동하면 구분 이동 시 루트 레이아웃, 대시보드 레이아웃(사이드바) 그대로 유지 {children} 자리의 페이지 customers/page.tsx → invoices/page.tsx 로 교체 바뀌는 부분만 다시 그리기 때문에 빠르고, 사이드바에 있는 상태(입력값 등)도 유지된다. 단, 이건 감싸는 구조 + <Link> 로 이동할 때 일어난다. 브라우저 새로고침이나 <a> 태그로 이동하면 페이지 전체를 처음부터 다시 불러오기 때문에 레이아웃도 다시 그려진다. 3. <Link> 와 페이지 이동 처음 사이드바는 <a> 태그로 되어 있어서 메뉴를 누를 때마다 페이지 전체가 새로고침 됐다. next/link 의 <Link> 로 바꾸면 해결된다. import Link from 'next/link'; <Link href="/dashboard/invoices">Invoices</Link> 고객 → 청구서 클릭 한 번에 일어나는 일 Code-splitting : Next.js는 코드를 페이지별로 나눠둔다. 처음 접속할 때 전체 앱이 아니라 지금 페이지 코드만 받는다. Prefetching : 화면에 <Link> 가 보이면, 그 링크의 페이지 코드를 미리 받아둔다 . (배포 환경에서 동작) Client-side Navigation : 클릭하면 서버에 페이지 전체를 다시 요청하는 대신, 브라우저(JS)가 {children} 자리만 교체한다. 그 결과 Partial Rendering 이 일어나서 사이드바는 그대로, 오른쪽 내용만 바뀐다. 즉 <Link> 는 이 기능들을 쓰기 위한 입구 이고, 각 기능이 합쳐져서 "깜빡임 없는 이동"이 된다. <Link> 는 CSS로 버튼처럼 꾸밀 수 있지만 역할은 페이지 이동 이다. 저장·삭제처럼 어떤 동작을 실행 하는 건 <button> 을 쓴다. 4. 활성 링크 — usePathname() 과 'use client' 지금 있는 페이지의 메뉴를 파란색으로 표시하는 기능이다. 이번 주에 제일 헷갈린 부분이라 역할을 하나씩 나눠서 정리했다. 먼저, 서버 컴포넌트와 클라이언트 컴포넌트 App Router의 컴포넌트는 기본적으로 서버 컴포넌트 다. 구분 실행 위치 할 수 있는 것 서버 컴포넌트 (기본값) 서버 화면을 미리 만들어 보내기. 브라우저 기능은 사용 불가 클라이언트 컴포넌트 브라우저 클릭, 입력, 현재 주소 확인 등 사용자와 상호작용 각자 맡은 역할 코드 역할 'use client' "이 파일은 브라우저에서도 실행되는 클라이언트 컴포넌트"라는 표시 usePathname() 현재 URL 경로를 읽어오는 훅 pathname 읽어온 경로를 담는 변수 pathname === link.href 현재 경로와 메뉴 주소가 같은지 비교 clsx(...) 비교 결과가 참일 때만 파란색 클래스를 붙이기 usePathname() 은 브라우저 주소창을 봐야 하는 기능이라 클라이언트 컴포넌트에서만 쓸 수 있다. 그래서 usePathname() 을 쓰려면 파일 맨 위에 'use client' 가 꼭 필요하다. 둘은 대신할 수 있는 관계가 아니라 "사용 조건"과 "실제 기능" 의 관계다. 실제로 색이 바뀌는 과정 'use client'; // 1 클라이언트 컴포넌트로 지정 import Link from 'next/link'; import { usePathname } from 'next/navigation'; import clsx from 'clsx'; // links 배열(메뉴 이름·주소·아이콘)은 생략 export default function NavLinks() { const pathname = usePathname(); // 2 현재 경로 읽기 return ( <> {links.map((link) => ( <Link key={link.name} href={link.href} className={clsx( 'flex h-[48px] ... bg-gray-50 ...', // 기본 스타일 (항상) { 'bg-sky-100 text-blue-600': pathname === link.href, // 3 비교 → 4 같을 때만 파란색 }, )} > {/* 아이콘, 메뉴 이름 */} </Link> ))} </> ); } /dashboard/invoices 에 있을 때: pathname = "/dashboard/invoices" Invoices 메뉴: link.href 가 같음 → bg-sky-100 text-blue-600 적용 Home, Customers 메뉴: 다름 → 기본 스타일만 usePathname() 이 색을 바꾸는 게 아니다. 경로를 알려주기만 하고, 색을 정하는 건 비교 + clsx 다. 5. GitHub · Vercel · PostgreSQL 연결 흐름 [내 컴퓨터 코드] ──push──▶ [GitHub] ──연결──▶ [Vercel 배포] │ 연결 ▼ [Neon PostgreSQL DB] GitHub : Vercel은 내 컴퓨터가 아니라 GitHub에서 코드를 가져간다. Vercel : GitHub 저장소를 Import해서 배포. 이후 main 에 push하면 자동으로 재배포 된다. Neon : Vercel의 Storage → Create Database에서 생성하고 프로젝트에 연결. Region은 Vercel 서버 기본 위치와 같은 Washington, D.C.(iad1) 로 골랐다. 사용자 위치가 아니라 서버와 DB가 가까운 게 기준이다. Seed 데이터 빈 DB에 처음 넣는 연습용 데이터다. app/lib/placeholder-data.ts 의 데이터를 localhost:3000/seed 에 접속해서 넣었다. route.ts 와 page.tsx /seed , /query 는 page.tsx 가 아니라 route.ts (Route Handler)다. 파일 하는 일 결과 page.tsx 화면 구성 HTML 화면 route.ts 요청 받기 → 처리/DB 조회 → 응답 JSON 같은 데이터 // app/query/route.ts export async function GET() { try { return Response.json(await listInvoices()); // DB 조회 결과를 JSON으로 응답 } catch (error) { return Response.json({ error }, { status: 500 }); } } 브라우저로 /query 에 접속하면 화면 대신 이런 데이터가 그대로 보인다. [{"amount":666,"name":"Evil Rabbit"}] 모든 페이지가 route.ts 를 거쳐야 하는 건 아니다. 이번엔 DB 연결 확인용으로 썼고, 대시보드 화면에서 데이터를 가져오는 방법은 다음 챕터(Ch.7 Fetching Data)에서 다룬다. 6. 환경변수와 Secret 관리 환경변수, .env , Vercel 설정의 관계 환경변수 : 코드 밖에 두는 설정값. POSTGRES_URL 은 이름 이고, 실제 값 에는 DB 주소·아이디·비밀번호가 들어간다. 코드에서는 이름으로만 꺼내 쓴다. const sql = postgres(process.env.POSTGRES_URL!, { ssl: 'require' }); 같은 POSTGRES_URL 이 두 장소에 보관 된다. 실행 환경 값을 읽는 곳 개발 ( pnpm dev ) 내 컴퓨터의 .env 파일 배포 (Vercel) Vercel 프로젝트의 Environment Variables 배포용이 별도 기능이 아니라 같은 설정을 다른 장소에 둔 것 이다. Vercel 서버는 내 컴퓨터의 .env 를 볼 수 없기 때문에 따로 필요하다. 두 곳에 같은 접속 정보가 있으니 개발할 때도, 배포했을 때도 같은 DB 에 연결된다. 실습에서 맞춘 설정 Neon 연결 시 Custom Prefix를 POSTGRES 로 입력 → Vercel에 POSTGRES_URL 이 자동 등록됨. 교재 코드가 이 이름을 찾기 때문. "Create database branch for deployment"는 체크 해제 → 체크하면 배포용 DB 복사본이 따로 생겨서, 로컬에서 Seed한 데이터가 배포 사이트에서 안 보이게 된다. .env 라는 이름이 비밀을 지켜주는 건 아니다 .env 도 그냥 파일이라 git add 하면 GitHub에 올라간다. 막아주는 건 .gitignore 다. # .gitignore .env*.local .env push 전에 git status 로 .env 가 목록에 없는지 확인했다. 그리고 Vercel에서 값을 복사할 때는 Show secret 을 먼저 눌러야 **** 가 아닌 실제 값이 복사된다. 7. 실습하며 겪은 문제 1 /query 결과가 2개 나옴 원인: /seed 가 두 번 실행됨. users · customers · revenue 는 고정된 값(ID, 월)이 있어서 이미 있으면 건너뛰지만, invoices 는 실행할 때마다 ID가 새로 만들어져서 중복으로 들어감 해결: Vercel Storage → Query에서 Read-only를 끄고 DROP TABLE invoices; 실행 → /seed 한 번만 다시 접속 2 배포 사이트에서 /query 가 옛날 안내 문구를 보여줌 원인: 이전 배포의 배포별 고정 주소 로 접속함. 이 주소는 그 시점 버전에 고정되어 있음 해결: 프로젝트 Domains의 Production 주소 (항상 최신 배포)로 접속 3 배포 전에 app/seed 삭제 그대로 배포하면 누구나 배포주소/seed 에 접속해서 Seed를 실행할 수 있다(1 같은 중복 발생). Git 기록에 남아 있으니 필요하면 되살릴 수 있다. 마무리 URL과 폴더 구조 대응: /dashboard , /dashboard/customers , /dashboard/invoices 배포 환경에서 Seed 데이터 조회: https://next-js-dashboard-iota-lime.vercel.app/query 이번 주에 가장 크게 정리된 건 "누가 무엇을 담당하는지" 였다. usePathname() 은 경로를 읽기만 하고 색은 clsx 가 정하는 것, .env 가 아니라 .gitignore 가 비밀을 지키는 것처럼 역할을 나눠서 보니 헷갈리던 게 풀렸다. 다음 주는 Ch.7부터 DB 데이터를 실제 대시보드 화면에 가져온다.
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주차] App Router 라우팅과 DB 연결 (Ch.4~6). 환경: Next.js 16.0.10 · TypeScript · pnpm 교재: Next.js Learn 대시보드 강좌 Chapter 4: Creating Layouts and Pages Chapter 5: Navigating Between Pages Chapter 6: Setting Up Your Database 이번 주는 대시보드 페이지들을 만들고, 페이지 이동을 부드럽게 바꾸고, 실제 DB를 연결해서 배포까지 해봤다. 1. 폴더 기반 라우팅과 page.tsx Next.js(App Router)는 폴더 구조가 곧 URL 이다. 폴더 하나가 URL의 한 조각(Route Segment)이 된다. app/ ├──…
Open source