export default function AdminLayout({ children }) {
const [sidebarOpen, setSidebarOpen] = useState(true)
{/* 관리자 헤더 */}
<button
onClick={() => setSidebarOpen(!sidebarOpen)}
className="p-2 hover:bg-gray-800 rounded"
>
☰
Admin Panel
관리자님
로그아웃
<div className="flex pt-16">
{/* 사이드바 */}
<aside className={`bg-gray-800 text-white w-64 min-h-screen transition-all duration-300 ${
sidebarOpen ? 'translate-x-0' : '-translate-x-full'
} fixed lg:relative lg:translate-x-0`}>
<nav className="p-4">
<Link href="/admin" className="block py-2 px-4 hover:bg-gray-700 rounded">
대시보드
</Link>
<Link href="/admin/users" className="block py-2 px-4 hover:bg-gray-700 rounded">
사용자 관리
</Link>
<Link href="/admin/products" className="block py-2 px-4 hover:bg-gray-700 rounded">
상품 관리
</Link>
<Link href="/admin/orders" className="block py-2 px-4 hover:bg-gray-700 rounded">
주문 관리
</Link>
<Link href="/admin/settings" className="block py-2 px-4 hover:bg-gray-700 rounded">
설정
</Link>
</nav>
</aside>
{/* 메인 콘텐츠 */}
<main className="flex-1 p-6">
{children}
</main>
</div>
</div>
)
}
동적 레이아웃 전환
조건에 따라 다른 레이아웃을 사용하는 방법입니다:
jsx
'use client'
import { useState, useEffect } from 'react'
import DefaultLayout from './DefaultLayout'
import CompactLayout from './CompactLayout'
import WideLayout from './WideLayout'
export default function DynamicLayout({ children, layoutType = 'default' }) {
const [currentLayout, setCurrentLayout] = useState(layoutType)
const [isMobile, setIsMobile] = useState(false)
useEffect(() => {
const checkMobile = () => {
setIsMobile(window.innerWidth < 768)
}
checkMobile()
window.addEventListener('resize', checkMobile)
return () => window.removeEventListener('resize', checkMobile)
}, [])
// 모바일에서는 항상 CompactLayout 사용
const activeLayout = isMobile ? 'compact' : currentLayout
const layouts = {
default: {children} ,
compact: {children} ,
wide: {children}
}
return (
{/* 레이아웃 선택기 */}
<button
onClick={() => setCurrentLayout('default')}
className={px-3 py-1 rounded ${ currentLayout === 'default' ? 'bg-blue-600 text-white' : 'bg-gray-200' }}
>
기본
<button
onClick={() => setCurrentLayout('compact')}
className={px-3 py-1 rounded ${ currentLayout === 'compact' ? 'bg-blue-600 text-white' : 'bg-gray-200' }}
>
컴팩트
<button
onClick={() => setCurrentLayout('wide')}
className={px-3 py-1 rounded ${ currentLayout === 'wide' ? 'bg-blue-600 text-white' : 'bg-gray-200' }}
>
와이드
{layouts[activeLayout]}
</div>
)
}
실습: 완성도 높은 레이아웃 시스템
모든 기능을 통합한 레이아웃 시스템을 구축해봅시다:
jsx
// components/layouts/MasterLayout.jsx
'use client'
import { createContext, useContext, useState } from 'react'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
// 레이아웃 컨텍스트
const LayoutContext = createContext()
export function useLayout() {
return useContext(LayoutContext)
}
export default function MasterLayout({ children, config = {} }) {
const [sidebarCollapsed, setSidebarCollapsed] = useState(false)
const [theme, setTheme] = useState('light')
const pathname = usePathname()
const defaultConfig = {
showHeader: true,
showSidebar: false,
showFooter: true,
maxWidth: 'container',
padding: 'normal',
...config
}
const value = {
sidebarCollapsed,
setSidebarCollapsed,
theme,
setTheme,
config: defaultConfig
}
return (
<LayoutContext.Provider value={value}>
<div className={min-h-screen ${theme === 'dark' ? 'dark bg-gray-900' : 'bg-gray-50'}}>
{defaultConfig.showHeader &&
}
<div className="flex">
{defaultConfig.showSidebar && <Sidebar />}
<main className={`flex-1 ${
defaultConfig.maxWidth === 'container' ? 'container mx-auto' : 'w-full'
} ${
defaultConfig.padding === 'normal' ? 'p-6' : defaultConfig.padding === 'compact' ? 'p-3' : 'p-0'
}`}>
{/* 브레드크럼 */}
<Breadcrumbs />
{/* 페이지 콘텐츠 */}
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-sm p-6">
{children}
</div>
</main>
</div>
{defaultConfig.showFooter && <Footer />}
</div>
</LayoutContext.Provider>
)
}
function Header() {
const { theme, setTheme } = useLayout()
return (
MyApp
<nav className="flex items-center space-x-6">
<Link href="/dashboard" className="text-gray-600 dark:text-gray-300 hover:text-blue-600">
대시보드
</Link>
<Link href="/projects" className="text-gray-600 dark:text-gray-300 hover:text-blue-600">
프로젝트
</Link>
<Link href="/team" className="text-gray-600 dark:text-gray-300 hover:text-blue-600">
팀
</Link>
{/* 테마 토글 */}
<button
onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
className="p-2 rounded hover:bg-gray-100 dark:hover:bg-gray-700"
>
{theme === 'light' ? '🌙' : '☀️'}
</button>
</nav>
</div>
</header>
)
}
function Sidebar() {
const { sidebarCollapsed, setSidebarCollapsed } = useLayout()
const pathname = usePathname()
const menuItems = [
{ icon: '🏠', label: '홈', href: '/' },
{ icon: '📊', label: '대시보드', href: '/dashboard' },
{ icon: '📁', label: '프로젝트', href: '/projects' },
{ icon: '👥', label: '팀', href: '/team' },
{ icon: '⚙️', label: '설정', href: '/settings' }
]
return (
<aside className={bg-gray-900 text-white transition-all duration-300 ${ sidebarCollapsed ? 'w-20' : 'w-64' }}>
<button
onClick={() => setSidebarCollapsed(!sidebarCollapsed)}
className="w-full text-left mb-6"
>
{sidebarCollapsed ? '→' : '←'}
<nav className="space-y-2">
{menuItems.map(item => (
<Link
key={item.href}
href={item.href}
className={`flex items-center space-x-3 px-3 py-2 rounded transition ${
pathname === item.href
? 'bg-blue-600'
: 'hover:bg-gray-800'
}`}
>
<span className="text-xl">{item.icon}</span>
{!sidebarCollapsed && <span>{item.label}</span>}
</Link>
))}
</nav>
</div>
</aside>
)
}
function Breadcrumbs() {
const pathname = usePathname()
const paths = pathname.split('/').filter(Boolean)
if (paths.length === 0) return null
return (
홈
{paths.map((path, index) => (
/
{index === paths.length - 1 ? (
{path}
) : (
<Link href={/${paths.slice(0, index + 1).join('/')}} className="hover:text-blue-600">
{path}
)}
))}
)
}
function Footer() {
return (
)
}
결론
Layout 컴포넌트는 웹 애플리케이션의 일관성과 유지보수성을 크게 향상시킵니다. 이번 챕터에서 우리는 기본 레이아웃 구조부터 중첩 레이아웃, 동적 레이아웃 전환, 그리고 완성도 높은 레이아웃 시스템까지 구현해보았습니다.
잘 설계된 레이아웃 시스템은 개발 생산성을 높이고, 사용자 경험을 일관되게 유지하며, 새로운 기능을 추가할 때도 쉽게 확장할 수 있습니다. Next.js의 파일 기반 레이아웃 시스템을 활용하면 이 모든 것을 효율적으로 구현할 수 있습니다.
다음 챕터에서는 이미지 최적화와 처리에 대해 알아보겠습니다. Next.js의 Image 컴포넌트를 활용하여 성능을 최적화하는 방법을 배워보겠습니다!