본문으로 건너뛰기
Advertisement

환경 설정 — create-next-app, 폴더 구조, next.config.js 주요 설정

프로젝트 생성: create-next-app

Next.js 공식 CLI 도구인 create-next-app으로 프로젝트를 초기화합니다.

npx create-next-app@latest my-app

실행하면 대화형 프롬프트가 나타납니다:

What is your project named? my-app
Would you like to use TypeScript? › Yes
Would you like to use ESLint? › Yes
Would you like to use Tailwind CSS? › Yes
Would you like your code inside a `src/` directory? › No
Would you like to use App Router? (recommended) › Yes
Would you like to use Turbopack for `next dev`? › Yes
Would you like to customize the import alias (@/* by default)? › No

권장 설정: TypeScript, ESLint, App Router는 모두 Yes를 선택하세요. Tailwind CSS는 프로젝트 요구사항에 따라 선택하세요.

템플릿으로 생성

# 예제 템플릿 사용
npx create-next-app@latest my-app --example with-tailwindcss
npx create-next-app@latest my-app --example blog-starter

# 특정 버전 고정
npx create-next-app@15.0.0 my-app

기존 프로젝트에 Next.js 추가

npm install next@latest react@latest react-dom@latest

package.json에 스크립트 추가:

{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
}
}

생성된 폴더 구조

create-next-app이 생성하는 기본 구조입니다:

my-app/
├── app/ # App Router 루트
│ ├── favicon.ico
│ ├── globals.css # 전역 스타일
│ ├── layout.tsx # 루트 레이아웃
│ └── page.tsx # 홈 페이지 (/)
├── public/ # 정적 파일 (이미지, 폰트 등)
│ ├── next.svg
│ └── vercel.svg
├── .eslintrc.json
├── .gitignore
├── next.config.ts # Next.js 설정
├── package.json
├── postcss.config.mjs # Tailwind CSS 설정
├── tailwind.config.ts # Tailwind 커스터마이징
└── tsconfig.json # TypeScript 설정

권장 확장 폴더 구조

실제 프로덕션 프로젝트에서 권장하는 구조입니다:

my-app/
├── app/
│ ├── (auth)/ # 인증 관련 Route Group
│ │ ├── login/
│ │ │ └── page.tsx
│ │ └── register/
│ │ └── page.tsx
│ ├── (main)/ # 메인 콘텐츠 Route Group
│ │ ├── layout.tsx # 메인 레이아웃
│ │ ├── dashboard/
│ │ │ ├── page.tsx
│ │ │ └── loading.tsx
│ │ └── settings/
│ │ └── page.tsx
│ ├── api/ # Route Handlers
│ │ └── users/
│ │ └── route.ts
│ ├── globals.css
│ └── layout.tsx # 루트 레이아웃
├── components/ # 재사용 가능한 컴포넌트
│ ├── ui/ # 순수 UI 컴포넌트 (shadcn/ui 등)
│ │ ├── Button.tsx
│ │ └── Input.tsx
│ └── features/ # 기능별 컴포넌트
│ ├── auth/
│ └── dashboard/
├── lib/ # 유틸리티, 헬퍼
│ ├── utils.ts
│ ├── db.ts # DB 클라이언트
│ └── auth.ts # 인증 헬퍼
├── hooks/ # 커스텀 훅
│ └── useLocalStorage.ts
├── types/ # TypeScript 타입 정의
│ └── index.ts
├── public/
└── next.config.ts

src/ 디렉터리 사용

src/ 디렉터리를 선택한 경우:

my-app/
├── src/
│ ├── app/ # app/ 대신 src/app/
│ ├── components/
│ └── lib/
├── public/
└── next.config.ts

두 방식 모두 지원됩니다. src/를 사용하면 소스 코드와 설정 파일이 분리되어 더 깔끔합니다.


next.config.ts 주요 설정

Next.js 15부터 next.config.ts(TypeScript)를 기본으로 사용합니다.

기본 구조

// next.config.ts
import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
// 설정 옵션
};

export default nextConfig;

주요 설정 옵션

1. 이미지 도메인 허용 (images)

외부 이미지를 <Image> 컴포넌트로 최적화하려면 도메인을 허용해야 합니다.

const nextConfig: NextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'images.unsplash.com',
port: '',
pathname: '/**',
},
{
protocol: 'https',
hostname: '**.amazonaws.com', // 와일드카드 서브도메인
},
],
// 이미지 최적화 포맷 순서
formats: ['image/avif', 'image/webp'],
// 최소 캐시 유지 시간 (초)
minimumCacheTTL: 3600,
},
};

2. 환경 변수 노출 (env)

const nextConfig: NextConfig = {
env: {
// 클라이언트에 노출되는 변수 (NEXT_PUBLIC_ 없이도 가능)
APP_VERSION: process.env.npm_package_version,
BUILD_TIME: new Date().toISOString(),
},
};

주의: 서버 전용 환경 변수는 process.env.MY_SECRET처럼 Server Component에서만 접근하세요. 클라이언트에 노출하려면 반드시 NEXT_PUBLIC_ 접두사를 사용하세요.

3. 리다이렉트 (redirects)

const nextConfig: NextConfig = {
async redirects() {
return [
{
source: '/old-blog/:slug',
destination: '/blog/:slug',
permanent: true, // 301 (영구), false = 307 (임시)
},
{
source: '/docs',
destination: '/docs/introduction',
permanent: false,
},
// 조건부 리다이렉트
{
source: '/admin',
has: [
{
type: 'cookie',
key: 'auth-token',
value: undefined, // 쿠키가 없을 때
},
],
destination: '/login',
permanent: false,
},
];
},
};

4. 리라이트 (rewrites)

URL은 바꾸지 않고 실제로 다른 경로로 처리합니다. 프록시 설정에 유용합니다.

const nextConfig: NextConfig = {
async rewrites() {
return [
// /api/v1/* → 외부 API로 프록시
{
source: '/api/v1/:path*',
destination: 'https://api.external.com/:path*',
},
// 레거시 URL 지원
{
source: '/old-products/:id',
destination: '/products/:id',
},
];
},
};

5. 보안 헤더 (headers)

const nextConfig: NextConfig = {
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'X-Frame-Options',
value: 'DENY',
},
{
key: 'X-Content-Type-Options',
value: 'nosniff',
},
{
key: 'Referrer-Policy',
value: 'strict-origin-when-cross-origin',
},
{
key: 'Permissions-Policy',
value: 'camera=(), microphone=(), geolocation=()',
},
{
key: 'Content-Security-Policy',
value: [
"default-src 'self'",
"script-src 'self' 'unsafe-eval' 'unsafe-inline'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https:",
].join('; '),
},
],
},
{
// API 라우트에 CORS 헤더 추가
source: '/api/:path*',
headers: [
{ key: 'Access-Control-Allow-Origin', value: '*' },
{ key: 'Access-Control-Allow-Methods', value: 'GET, POST, PUT, DELETE, OPTIONS' },
{ key: 'Access-Control-Allow-Headers', value: 'Content-Type, Authorization' },
],
},
];
},
};

6. 번들 분석기 설정

// next.config.ts
import type { NextConfig } from 'next';
import bundleAnalyzer from '@next/bundle-analyzer';

const withBundleAnalyzer = bundleAnalyzer({
enabled: process.env.ANALYZE === 'true',
});

const nextConfig: NextConfig = {
// ...
};

export default withBundleAnalyzer(nextConfig);
# 번들 분석 실행
ANALYZE=true npm run build

7. 실험적 기능 (experimental)

const nextConfig: NextConfig = {
experimental: {
// Partial Prerendering (PPR)
ppr: 'incremental',
// React 컴파일러 (자동 메모이제이션)
reactCompiler: true,
// 타입 안전한 Link
typedRoutes: true,
// 서버 액션 파일 업로드 크기 제한
serverActions: {
bodySizeLimit: '10mb',
},
},
};

8. 출력 설정 (output)

const nextConfig: NextConfig = {
// 독립 실행형 빌드 (Docker 배포 최적화)
output: 'standalone',

// 또는 완전 정적 출력
// output: 'export',
// trailingSlash: true, // export 모드에서 권장
};

환경 변수 관리

.env 파일 계층

.env                  # 모든 환경 (git 커밋)
.env.local # 로컬 오버라이드 (.gitignore에 포함)
.env.development # 개발 환경 (npm run dev)
.env.production # 프로덕션 (npm run build)
.env.test # 테스트 환경

우선순위: .env.local > .env.{NODE_ENV} > .env

환경 변수 예시

# .env.local
DATABASE_URL=postgresql://localhost:5432/mydb
API_SECRET_KEY=super-secret-key-here

# 클라이언트에 노출 (NEXT_PUBLIC_ 필수)
NEXT_PUBLIC_API_URL=https://api.example.com
NEXT_PUBLIC_GA_ID=G-XXXXXXXXXX

TypeScript에서 환경 변수 타입 안전성

// lib/env.ts — 환경 변수 검증
import { z } from 'zod';

const envSchema = z.object({
DATABASE_URL: z.string().url(),
API_SECRET_KEY: z.string().min(32),
NEXT_PUBLIC_API_URL: z.string().url(),
NODE_ENV: z.enum(['development', 'production', 'test']),
});

// 빌드 타임에 환경 변수 검증
export const env = envSchema.parse(process.env);

TypeScript 설정 (tsconfig.json)

create-next-app이 생성하는 기본 tsconfig.json:

{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}

유용한 추가 설정

{
"compilerOptions": {
// ...기본 설정...

// 더 엄격한 타입 체크
"noUncheckedIndexedAccess": true,
"noImplicitReturns": true,
"exactOptionalPropertyTypes": true,

// 절대 경로 별칭 추가
"paths": {
"@/*": ["./*"],
"@components/*": ["./components/*"],
"@lib/*": ["./lib/*"],
"@types/*": ["./types/*"]
}
}
}

ESLint 설정

Next.js는 next/core-web-vitals 규칙 세트를 기본으로 포함합니다.

// .eslintrc.json
{
"extends": ["next/core-web-vitals", "next/typescript"]
}

커스텀 ESLint 규칙 추가

{
"extends": ["next/core-web-vitals", "next/typescript"],
"rules": {
// console.log 경고
"no-console": ["warn", { "allow": ["warn", "error"] }],
// 미사용 변수 에러
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
// any 타입 사용 경고
"@typescript-eslint/no-explicit-any": "warn"
}
}

실전 예제: 완전한 프로젝트 초기 설정

1. 프로젝트 생성

npx create-next-app@latest my-production-app \
--typescript \
--tailwind \
--eslint \
--app \
--turbopack \
--import-alias "@/*"

2. 추가 패키지 설치

cd my-production-app

# UI 및 스타일링
npm install clsx tailwind-merge

# 폼 처리
npm install react-hook-form @hookform/resolvers zod

# 데이터 페칭 (클라이언트)
npm install @tanstack/react-query

# 인증
npm install next-auth@beta

# 데이터베이스 (예: Prisma + PostgreSQL)
npm install prisma @prisma/client
npm install -D @types/node

3. 완전한 next.config.ts 예시

// next.config.ts
import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
// 이미지 최적화
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'images.unsplash.com',
},
{
protocol: 'https',
hostname: '**.cloudinary.com',
},
],
formats: ['image/avif', 'image/webp'],
},

// 보안 헤더
async headers() {
return [
{
source: '/(.*)',
headers: [
{ key: 'X-Frame-Options', value: 'DENY' },
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
],
},
];
},

// 리다이렉트
async redirects() {
return [
{
source: '/home',
destination: '/',
permanent: true,
},
];
},

// 실험적 기능
experimental: {
ppr: 'incremental',
reactCompiler: true,
typedRoutes: true,
},

// Docker 배포용 독립 빌드
output: 'standalone',
};

export default nextConfig;

4. 루트 레이아웃 초기 설정

// app/layout.tsx
import type { Metadata, Viewport } from 'next';
import { Inter, Noto_Sans_KR } from 'next/font/google';
import './globals.css';

const inter = Inter({
subsets: ['latin'],
variable: '--font-inter',
display: 'swap',
});

const notoSansKR = Noto_Sans_KR({
subsets: ['latin'],
variable: '--font-noto-sans-kr',
display: 'swap',
});

export const metadata: Metadata = {
metadataBase: new URL('https://myapp.com'),
title: {
template: '%s | My App',
default: 'My App — 최고의 서비스',
},
description: '당신의 비즈니스를 위한 최고의 솔루션',
keywords: ['Next.js', 'React', 'TypeScript'],
authors: [{ name: 'My Team', url: 'https://myapp.com' }],
openGraph: {
type: 'website',
locale: 'ko_KR',
url: 'https://myapp.com',
siteName: 'My App',
images: [
{
url: '/og-image.png',
width: 1200,
height: 630,
alt: 'My App OG Image',
},
],
},
twitter: {
card: 'summary_large_image',
site: '@myapp',
},
robots: {
index: true,
follow: true,
googleBot: {
index: true,
follow: true,
'max-video-preview': -1,
'max-image-preview': 'large',
'max-snippet': -1,
},
},
};

export const viewport: Viewport = {
themeColor: [
{ media: '(prefers-color-scheme: light)', color: '#ffffff' },
{ media: '(prefers-color-scheme: dark)', color: '#0f172a' },
],
width: 'device-width',
initialScale: 1,
};

export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html
lang="ko"
className={`${inter.variable} ${notoSansKR.variable}`}
suppressHydrationWarning
>
<body className="min-h-screen bg-background font-sans antialiased">
{children}
</body>
</html>
);
}

5. 개발 서버 실행

# Turbopack으로 빠른 개발 서버 실행 (Next.js 15 기본값)
npm run dev

# 브라우저에서 접근
# http://localhost:3000

개발 도구 설정

VS Code 확장 프로그램

// .vscode/extensions.json
{
"recommendations": [
"bradlc.vscode-tailwindcss", // Tailwind IntelliSense
"esbenp.prettier-vscode", // Prettier 포매터
"dbaeumer.vscode-eslint", // ESLint
"ms-vscode.vscode-typescript-next" // TypeScript 최신 버전
]
}

VS Code 설정

// .vscode/settings.json
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true,
"tailwindCSS.experimental.classRegex": [
["clsx\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"],
["cn\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"]
]
}

Prettier 설정

// .prettierrc
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 100,
"plugins": ["prettier-plugin-tailwindcss"]
}

고수 팁

1. 경로 별칭으로 깔끔한 임포트

// tsconfig.json의 paths 설정
{
"paths": {
"@/*": ["./*"],
"@components/*": ["./components/*"],
"@lib/*": ["./lib/*"],
"@types/*": ["./types/*"],
"@hooks/*": ["./hooks/*"]
}
}
// 이전: 상대 경로로 지저분한 임포트
import Button from '../../../components/ui/Button';

// 이후: 절대 경로로 깔끔한 임포트
import Button from '@components/ui/Button';

2. 멀티 설정 합성 패턴

// next.config.ts
import type { NextConfig } from 'next';

// 플러그인 패턴으로 설정 조합
function withSecurity(config: NextConfig): NextConfig {
return {
...config,
async headers() {
const existing = (await config.headers?.()) ?? [];
return [
...existing,
{
source: '/(.*)',
headers: [{ key: 'X-Frame-Options', value: 'DENY' }],
},
];
},
};
}

function withPerformance(config: NextConfig): NextConfig {
return {
...config,
images: {
...config.images,
formats: ['image/avif', 'image/webp'],
},
};
}

const baseConfig: NextConfig = {
experimental: { ppr: 'incremental' },
};

export default withSecurity(withPerformance(baseConfig));

3. 환경별 next.config.ts 분기

// next.config.ts
import type { NextConfig } from 'next';

const isDev = process.env.NODE_ENV === 'development';
const isProd = process.env.NODE_ENV === 'production';

const nextConfig: NextConfig = {
// 프로덕션에서만 독립 빌드
...(isProd && { output: 'standalone' }),

// 개발에서만 소스맵 활성화
...(isDev && { productionBrowserSourceMaps: true }),

experimental: {
ppr: isProd ? true : 'incremental',
reactCompiler: isProd,
},
};

export default nextConfig;

4. Turbopack 전용 설정

const nextConfig: NextConfig = {
// Turbopack 설정 (next dev --turbopack 시 적용)
turbopack: {
rules: {
// SVG를 React 컴포넌트로 임포트
'*.svg': {
loaders: ['@svgr/webpack'],
as: '*.js',
},
},
resolveAlias: {
// 모듈 별칭
underscore: 'lodash',
},
},
};

5. 빌드 성능 체크

# 빌드 시간 측정
time npm run build

# 번들 크기 분석
ANALYZE=true npm run build

# 타입 체크만 실행 (빌드 없이)
npx tsc --noEmit

# 린트만 실행
npm run lint
Advertisement