Dashboard의 Layout에 해당하는 Header 컴포넌트를 만들어야 한다.
이 Header에는 다른 페이지로 이동 할 수 있는 Navigation의 기능도 포함한다.
import { Header } from "@/components/ui/header";
type Props = {
children: React.ReactNode;
};
const DashboardLayout = ({ children }: Props) => {
return (
<>
<Header />
<main className="px-3 lg:px-14">{children}</main>
</>
);
};
export default DashboardLayout;
Header.tsx
import { ClerkLoaded, ClerkLoading, UserButton } from "@clerk/nextjs";
import { HeaderLogo } from "./header-logo";
import { Navigation } from "./navigation";
import { Loader2 } from "lucide-react";
import { WelcomeMsg } from "./welcome-msg";
export const Header = () => {
return (
<header className="bg-gradient-to-b from-blue-700 to-blue-500 px-4 py-8 lg:px-14 pb-36">
<div className="max-w-screen-2xl mx-auto">
<div className="w-full flex itmes-center justify-between mv-14">
<div className="flex items-center lg:gap-x-16">
<HeaderLogo />
<Navigation />
</div>
<ClerkLoaded>
<UserButton afterSignOutUrl="/" />
</ClerkLoaded>
<ClerkLoading>
<Loader2 className="size-8 animate-spin text-slate-400" />
</ClerkLoading>
</div>
<WelcomeMsg />
</div>
</header>
);
};
Header의 전체 형태를 보면
HeaderLogo와 Navigation이 묶여있는 하나의 div와 Clerk auth컴포넌트가 justify-between으로 나란히 배열되어 있다.
그리고 그 아래에 WecomeMessage가 나열되어 있다.
HeaderLogo.tsx
import Link from "next/link";
import Image from "next/image";
export const HeaderLogo = () => {
return (
<Link href="/">
<div className="items-center hidden lg:flex">
<Image src="/logo.svg" alt="Logo" height={28} width={28} />
<p className="font-semibold text-white text-2xl ml-2.5">Finance</p>
</div>
</Link>
);
};
Navigation.tsx
네비게이션 컴포넌트는 반응형 네비게이션 메뉴를 구현한 React함수형 컴포넌트이다. Next.js와 라이브러리를 활용해 화면 크기와 사용자의 인터렉션에 따라 다른 UI를 렌더링 하는 특징이 있다.
먼저 사용된 라이브러리들을 먼저 확인해보자
1. next/navigation
- useRouter: 페이지의 이동을 처리하는 훅이다.
- usePathname:현재 페이지의 URL경로를 가져온다.
2. react-use
- useMedia: 현재 화면ㅇ 크기를 감지해 모바일 환경인지 판별할 수 있게 한다
3. lucide-react
- 아이콘을 렌더링 한다.
컴포넌트 로직
1. routes배열
const routes = [
{ href: "/", label: "Overview" },
{ href: "/transactions", label: "Transactions" },
{ href: "/accounts", label: "Accounts" },
{ href: "/categories", label: "Categories" },
{ href: "/settings", label: "Settings" },
];
- href : 각 메뉴가 이동할 URL이다.
- label: 각 메뉴의 텍스트
2. isMobile반응형동작처리
- useMedia("(max-width:1024px)",false)를 이용해 화면 크기가 1024px이하인지 판별한다.
- 모바일 환경(isMobile===true)과 데스크탑 환경에서 각각 다른 UI를 반환한다.
모바일 환경 (1024px 이하)
1. sheet컴포넌트
- sheet은 좌측에서 슬라이드로 나타나는 메뉴다.
- isOpen상태를 통해 메뉴가 열리거나 닫힌다.
<Sheet open={isOpen} onOpenChange={setIsOpen}>
- sheetTrigger: 메뉴를 열기 위한 버튼
- sheetContent: 메뉴가 열렸을 때 표시되는 항목들
2. Button과 Menu아이콘
- SheetTrigger내부에 버튼을 렌더링하며 Menu아이콘으로 네비게시연 트리거 역할을 수행한다.
<Button
variant="outline"
size="sm"
className="font-normal bg-white/10 hover:bg-white/20 ..."
>
<Menu className="size-4" />
</Button>
3. 메뉴 항목 렌더링
- routes배열을 기반으로 메뉴 버튼을 렌더링한다.
{routes.map((route) => (
<Button
key={route.href}
variant={route.href === pathname ? "secondary" : "ghost"}
onClick={() => onClick(route.href)}
className="w-full justify-start"
>
{route.label}
</Button>
))}
- 현재 경로 (pathname)과 route.href가 같다면 "secondary"스타일로 강조한다.
- 버튼 클릭 시 onclick을 호출해 페이지 이동과 메뉴 닫기를 처리한다.
데스크탑환경 (1023px 초과)
1. navButton컴포넌트
- 데스크탑 환경에서는 routes배열을 기반으로 각 메뉴 버튼을 렌더링한다.
<nav className="hidden lg:flex items-center gap-x-2 overflow-x-auto">
{routes.map((route) => (
<NavButton
key={route.href}
href={route.href}
label={route.label}
isActive={pathname === route.href}
/>
))}
</nav>
- 현재 URL경로(pathname)와 메뉴 항목의 href를 비교해 활성 상태를 나타낸다.
- NavButton은 각 메뉴를 정렬하고 스타일링한다.
2. 반응형 처리
- className="hidden lg:flex"로 모바일에서는 숨기고 데스크탑에서만 보이도록 설정한다.
핵심함수들
1. onClick함수
- href로 페이지를 이동시키고, 모바일 환경에서 메뉴를 닫느다.
const onClick = (href: string) => {
router.push(href); // 페이지 이동
setIsOpen(false); // 메뉴 닫기
};
2. usePathname과 useRouter활용
- 현재 경로(pathname)를 가져와 활성 메뉴를 결정한다.
- router.push를 통해 Next.js라우팅한다.
Hono를 사용해 API구축하기
간단한 Next.js와 Clerk통합
추가적으로 이번 프로젝트에서는 hono를 사용하여 api를 구현하게된다.
1. HONO: 빠르고 간결한 API 라우팅을 제공하는 초경량 웹 프레임워크이다. Next.js와 함께 사용할 때 Edge Runtime과도 호환되어 성능에 민감한 프로젝트에서 적합하다.
2. Clerk : Clerk는 사용자 인증 및 관리를 간단하게 처리할 수 있도록 도와주는 서비스이다.
clerkMiddleware와 getAuth를 통해 사용자 인증 상태를 확인할 수 있다.
3. Zod : zod는 데이터 스키마 정의 및 검증을 위한 라이브러리이다. 타입스크립트와 긴밀하게 통합되어 API입력 데이터의 안정성과 타입 안정성을 보장한다.
Hono인스턴스 생성 및 기본 경로 설정
const app = new Hono().basePath("/api");
- hono인스턴스를 생성하고 .basePath("/api")로 기본경로를 /api로 설정한다. 이를통해 모든 엔드 포인트가 /api로 시작한다.
Clerk미들웨어를 이용한 인증 처리
app.get("/hello", clerkMiddleware(), (c) => {
const auth = getAuth(c); // Clerk로부터 인증 정보 가져오기
if (!auth?.userId) {
return c.json({ error: "Unauthorized" }); // 인증되지 않은 요청 처리
}
return c.json({
message: "Hello Next.js!",
userId: auth.userId,
});
});
- /api/hello 엔드포인트는 Clerk미들웨어를 통해 인증된 사용자만 접근할 수 있다.
- clerkMiddleware()는 사용자 인증 상태를 확인하는 역할을 한다.
- getAuth(c)는 userId와 같은 인증 정보를 가져온다.
- 인증되지 않은 사용자는 401unauthorized 응답을 받는다.
- 인증된 사용자에게는 userId와 함께 간단한 환영 메시지를 반환한다.
GET과 POST핸들러
export const GET = handle(app);
export const POST = handle(app);
- handle(app)을 통해 Hono앱을 Vercel의 Edge Runtime에서 실행할 수 있는 핸들러로 변환한다.
- GET과 POST 두가지 HTTP메서드를 지원하도록 설정