mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
1187 lines
37 KiB
Markdown
1187 lines
37 KiB
Markdown
# NextJS
|
|
|
|
{{#include ../../banners/hacktricks-training.md}}
|
|
|
|
## Next.js 애플리케이션의 일반 아키텍처
|
|
|
|
### 일반적인 파일 구조
|
|
|
|
표준 Next.js 프로젝트는 라우팅, API 엔드포인트 및 정적 자산 관리를 용이하게 하는 특정 파일 및 디렉토리 구조를 따릅니다. 다음은 일반적인 레이아웃입니다:
|
|
```lua
|
|
my-nextjs-app/
|
|
├── node_modules/
|
|
├── public/
|
|
│ ├── images/
|
|
│ │ └── logo.png
|
|
│ └── favicon.ico
|
|
├── app/
|
|
│ ├── api/
|
|
│ │ └── hello/
|
|
│ │ └── route.ts
|
|
│ ├── layout.tsx
|
|
│ ├── page.tsx
|
|
│ ├── about/
|
|
│ │ └── page.tsx
|
|
│ ├── dashboard/
|
|
│ │ ├── layout.tsx
|
|
│ │ └── page.tsx
|
|
│ ├── components/
|
|
│ │ ├── Header.tsx
|
|
│ │ └── Footer.tsx
|
|
│ ├── styles/
|
|
│ │ ├── globals.css
|
|
│ │ └── Home.module.css
|
|
│ └── utils/
|
|
│ └── api.ts
|
|
├── .env.local
|
|
├── next.config.js
|
|
├── tsconfig.json
|
|
├── package.json
|
|
├── README.md
|
|
└── yarn.lock / package-lock.json
|
|
|
|
```
|
|
### 핵심 디렉토리 및 파일
|
|
|
|
- **public/:** 이미지, 글꼴 및 기타 파일과 같은 정적 자산을 호스팅합니다. 여기의 파일은 루트 경로(`/`)에서 접근할 수 있습니다.
|
|
- **app/:** 애플리케이션의 페이지, 레이아웃, 구성 요소 및 API 경로를 위한 중앙 디렉토리입니다. **App Router** 패러다임을 채택하여 고급 라우팅 기능과 서버-클라이언트 구성 요소 분리를 가능하게 합니다.
|
|
- **app/layout.tsx:** 애플리케이션의 루트 레이아웃을 정의하며, 모든 페이지를 감싸고 헤더, 푸터 및 내비게이션 바와 같은 일관된 UI 요소를 제공합니다.
|
|
- **app/page.tsx:** 루트 경로 `/`의 진입점 역할을 하며, 홈 페이지를 렌더링합니다.
|
|
- **app/\[route]/page.tsx:** 정적 및 동적 경로를 처리합니다. `app/` 내의 각 폴더는 경로 세그먼트를 나타내며, 해당 폴더 내의 `page.tsx`는 경로의 구성 요소에 해당합니다.
|
|
- **app/api/:** API 경로를 포함하며, HTTP 요청을 처리하는 서버리스 함수를 생성할 수 있습니다. 이러한 경로는 전통적인 `pages/api` 디렉토리를 대체합니다.
|
|
- **app/components/:** 다양한 페이지와 레이아웃에서 활용할 수 있는 재사용 가능한 React 구성 요소를 보관합니다.
|
|
- **app/styles/:** 구성 요소 범위 스타일링을 위한 전역 CSS 파일 및 CSS 모듈을 포함합니다.
|
|
- **app/utils/:** 애플리케이션 전반에서 공유할 수 있는 유틸리티 함수, 헬퍼 모듈 및 기타 비 UI 로직을 포함합니다.
|
|
- **.env.local:** 로컬 개발 환경에 특정한 환경 변수를 저장합니다. 이러한 변수는 버전 관리에 **커밋되지** 않습니다.
|
|
- **next.config.js:** webpack 구성, 환경 변수 및 보안 설정을 포함하여 Next.js 동작을 사용자 정의합니다.
|
|
- **tsconfig.json:** 프로젝트에 대한 TypeScript 설정을 구성하여 타입 검사 및 기타 TypeScript 기능을 활성화합니다.
|
|
- **package.json:** 프로젝트 종속성, 스크립트 및 메타데이터를 관리합니다.
|
|
- **README.md:** 프로젝트에 대한 문서 및 정보를 제공하며, 설정 지침, 사용 가이드 및 기타 관련 세부 정보를 포함합니다.
|
|
- **yarn.lock / package-lock.json:** 프로젝트의 종속성을 특정 버전으로 고정하여 다양한 환경에서 일관된 설치를 보장합니다.
|
|
|
|
## Next.js의 클라이언트 측
|
|
|
|
### `app` 디렉토리의 파일 기반 라우팅
|
|
|
|
`app` 디렉토리는 최신 Next.js 버전에서 라우팅의 초석입니다. 파일 시스템을 활용하여 경로를 정의하여 라우트 관리를 직관적이고 확장 가능하게 만듭니다.
|
|
|
|
<details>
|
|
|
|
<summary>루트 경로 / 처리</summary>
|
|
|
|
**파일 구조:**
|
|
```arduino
|
|
my-nextjs-app/
|
|
├── app/
|
|
│ ├── layout.tsx
|
|
│ └── page.tsx
|
|
├── public/
|
|
├── next.config.js
|
|
└── ...
|
|
```
|
|
**주요 파일:**
|
|
|
|
- **`app/page.tsx`**: 루트 경로 `/`에 대한 요청을 처리합니다.
|
|
- **`app/layout.tsx`**: 모든 페이지를 감싸는 애플리케이션의 레이아웃을 정의합니다.
|
|
|
|
**구현:**
|
|
```tsx
|
|
tsxCopy code// app/page.tsx
|
|
|
|
export default function HomePage() {
|
|
return (
|
|
<div>
|
|
<h1>Welcome to the Home Page!</h1>
|
|
<p>This is the root route.</p>
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
**설명:**
|
|
|
|
- **경로 정의:** `app` 디렉토리 바로 아래의 `page.tsx` 파일은 `/` 경로에 해당합니다.
|
|
- **렌더링:** 이 컴포넌트는 홈 페이지의 내용을 렌더링합니다.
|
|
- **레이아웃 통합:** `HomePage` 컴포넌트는 헤더, 푸터 및 기타 공통 요소를 포함할 수 있는 `layout.tsx`로 감싸져 있습니다.
|
|
|
|
</details>
|
|
|
|
<details>
|
|
|
|
<summary>기타 정적 경로 처리</summary>
|
|
|
|
**예시: `/about` 경로**
|
|
|
|
**파일 구조:**
|
|
```arduino
|
|
arduinoCopy codemy-nextjs-app/
|
|
├── app/
|
|
│ ├── about/
|
|
│ │ └── page.tsx
|
|
│ ├── layout.tsx
|
|
│ └── page.tsx
|
|
├── public/
|
|
├── next.config.js
|
|
└── ...
|
|
```
|
|
**구현:**
|
|
```tsx
|
|
// app/about/page.tsx
|
|
|
|
export default function AboutPage() {
|
|
return (
|
|
<div>
|
|
<h1>About Us</h1>
|
|
<p>Learn more about our mission and values.</p>
|
|
</div>
|
|
)
|
|
}
|
|
```
|
|
**설명:**
|
|
|
|
- **경로 정의:** `about` 폴더 안의 `page.tsx` 파일은 `/about` 경로에 해당합니다.
|
|
- **렌더링:** 이 컴포넌트는 about 페이지의 내용을 렌더링합니다.
|
|
|
|
</details>
|
|
|
|
<details>
|
|
|
|
<summary>동적 경로</summary>
|
|
|
|
동적 경로는 변수 세그먼트가 있는 경로를 처리할 수 있게 하여, 애플리케이션이 ID, 슬러그 등과 같은 매개변수에 따라 내용을 표시할 수 있게 합니다.
|
|
|
|
**예시: `/posts/[id]` 경로**
|
|
|
|
**파일 구조:**
|
|
```arduino
|
|
arduinoCopy codemy-nextjs-app/
|
|
├── app/
|
|
│ ├── posts/
|
|
│ │ └── [id]/
|
|
│ │ └── page.tsx
|
|
│ ├── layout.tsx
|
|
│ └── page.tsx
|
|
├── public/
|
|
├── next.config.js
|
|
└── ...
|
|
```
|
|
**구현:**
|
|
```tsx
|
|
tsxCopy code// app/posts/[id]/page.tsx
|
|
|
|
import { useRouter } from 'next/navigation';
|
|
|
|
interface PostProps {
|
|
params: { id: string };
|
|
}
|
|
|
|
export default function PostPage({ params }: PostProps) {
|
|
const { id } = params;
|
|
// Fetch post data based on 'id'
|
|
|
|
return (
|
|
<div>
|
|
<h1>Post #{id}</h1>
|
|
<p>This is the content of post {id}.</p>
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
**설명:**
|
|
|
|
- **동적 세그먼트:** `[id]`는 경로에서 동적 세그먼트를 나타내며, URL에서 `id` 매개변수를 캡처합니다.
|
|
- **매개변수 접근:** `params` 객체는 동적 매개변수를 포함하며, 컴포넌트 내에서 접근할 수 있습니다.
|
|
- **경로 일치:** `/posts/*`와 일치하는 모든 경로, 예를 들어 `/posts/1`, `/posts/abc` 등이 이 컴포넌트에 의해 처리됩니다.
|
|
|
|
</details>
|
|
|
|
<details>
|
|
|
|
<summary>중첩 경로</summary>
|
|
|
|
Next.js는 중첩 라우팅을 지원하여 디렉토리 레이아웃을 반영하는 계층적 경로 구조를 허용합니다.
|
|
|
|
**예시: `/dashboard/settings/profile` 경로**
|
|
|
|
**파일 구조:**
|
|
```arduino
|
|
arduinoCopy codemy-nextjs-app/
|
|
├── app/
|
|
│ ├── dashboard/
|
|
│ │ ├── settings/
|
|
│ │ │ └── profile/
|
|
│ │ │ └── page.tsx
|
|
│ │ └── page.tsx
|
|
│ ├── layout.tsx
|
|
│ └── page.tsx
|
|
├── public/
|
|
├── next.config.js
|
|
└── ...
|
|
```
|
|
**구현:**
|
|
```tsx
|
|
tsxCopy code// app/dashboard/settings/profile/page.tsx
|
|
|
|
export default function ProfileSettingsPage() {
|
|
return (
|
|
<div>
|
|
<h1>Profile Settings</h1>
|
|
<p>Manage your profile information here.</p>
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
**설명:**
|
|
|
|
- **깊은 중첩:** `dashboard/settings/profile/` 내부의 `page.tsx` 파일은 `/dashboard/settings/profile` 경로에 해당합니다.
|
|
- **계층 반영:** 디렉토리 구조는 URL 경로를 반영하여 유지 관리성과 명확성을 향상시킵니다.
|
|
|
|
</details>
|
|
|
|
<details>
|
|
|
|
<summary>모든 경로 처리</summary>
|
|
|
|
모든 경로 처리는 여러 중첩 세그먼트 또는 알 수 없는 경로를 처리하여 경로 처리의 유연성을 제공합니다.
|
|
|
|
**예시: `/*` 경로**
|
|
|
|
**파일 구조:**
|
|
```arduino
|
|
my-nextjs-app/
|
|
├── app/
|
|
│ ├── [..slug]/
|
|
│ │ └── page.tsx
|
|
│ ├── layout.tsx
|
|
│ └── page.tsx
|
|
├── public/
|
|
├── next.config.js
|
|
└── ...
|
|
```
|
|
**구현:**
|
|
```tsx
|
|
// app/[...slug]/page.tsx
|
|
|
|
interface CatchAllProps {
|
|
params: { slug: string[] }
|
|
}
|
|
|
|
export default function CatchAllPage({ params }: CatchAllProps) {
|
|
const { slug } = params
|
|
const fullPath = `/${slug.join("/")}`
|
|
|
|
return (
|
|
<div>
|
|
<h1>Catch-All Route</h1>
|
|
<p>You have navigated to: {fullPath}</p>
|
|
</div>
|
|
)
|
|
}
|
|
```
|
|
**설명:**
|
|
|
|
- **Catch-All 세그먼트:** `[...slug]`는 모든 남은 경로 세그먼트를 배열로 캡처합니다.
|
|
- **사용법:** 사용자 생성 경로, 중첩 카테고리 등과 같은 동적 라우팅 시나리오를 처리하는 데 유용합니다.
|
|
- **경로 일치:** `/anything/here`, `/foo/bar/baz`와 같은 경로는 이 구성 요소에 의해 처리됩니다.
|
|
|
|
</details>
|
|
|
|
### 잠재적인 클라이언트 측 취약점
|
|
|
|
Next.js는 안전한 기반을 제공하지만, 부적절한 코딩 관행은 취약점을 초래할 수 있습니다. 주요 클라이언트 측 취약점은 다음과 같습니다:
|
|
|
|
<details>
|
|
|
|
<summary>교차 사이트 스크립팅 (XSS)</summary>
|
|
|
|
XSS 공격은 악성 스크립트가 신뢰할 수 있는 웹사이트에 주입될 때 발생합니다. 공격자는 사용자의 브라우저에서 스크립트를 실행하여 데이터를 훔치거나 사용자를 대신하여 작업을 수행할 수 있습니다.
|
|
|
|
**취약한 코드의 예:**
|
|
```jsx
|
|
// Dangerous: Injecting user input directly into HTML
|
|
function Comment({ userInput }) {
|
|
return <div dangerouslySetInnerHTML={{ __html: userInput }} />
|
|
}
|
|
```
|
|
**왜 취약한가:** 신뢰할 수 없는 입력과 함께 `dangerouslySetInnerHTML`을 사용하면 공격자가 악성 스크립트를 주입할 수 있습니다.
|
|
|
|
</details>
|
|
|
|
<details>
|
|
|
|
<summary>클라이언트 측 템플릿 주입</summary>
|
|
|
|
사용자 입력이 템플릿에서 부적절하게 처리될 때 발생하며, 공격자가 템플릿이나 표현식을 주입하고 실행할 수 있게 합니다.
|
|
|
|
**취약한 코드 예시:**
|
|
```jsx
|
|
import React from "react"
|
|
import ejs from "ejs"
|
|
|
|
function RenderTemplate({ template, data }) {
|
|
const html = ejs.render(template, data)
|
|
return <div dangerouslySetInnerHTML={{ __html: html }} />
|
|
}
|
|
```
|
|
**왜 취약한가:** `template` 또는 `data`에 악의적인 내용이 포함되면 의도하지 않은 코드 실행으로 이어질 수 있습니다.
|
|
|
|
</details>
|
|
|
|
<details>
|
|
|
|
<summary>클라이언트 경로 탐색</summary>
|
|
|
|
이는 공격자가 클라이언트 측 경로를 조작하여 Cross-Site Request Forgery (CSRF)와 같은 의도하지 않은 작업을 수행할 수 있게 하는 취약점입니다. 서버 측 경로 탐색이 서버의 파일 시스템을 목표로 하는 것과 달리, CSPT는 클라이언트 측 메커니즘을 악용하여 합법적인 API 요청을 악의적인 엔드포인트로 리다이렉트하는 데 중점을 둡니다.
|
|
|
|
**취약한 코드의 예:**
|
|
|
|
Next.js 애플리케이션은 사용자가 파일을 업로드하고 다운로드할 수 있도록 합니다. 다운로드 기능은 클라이언트 측에서 구현되어 있으며, 사용자가 다운로드할 파일 경로를 지정할 수 있습니다.
|
|
```jsx
|
|
// pages/download.js
|
|
import { useState } from "react"
|
|
|
|
export default function DownloadPage() {
|
|
const [filePath, setFilePath] = useState("")
|
|
|
|
const handleDownload = () => {
|
|
fetch(`/api/files/${filePath}`)
|
|
.then((response) => response.blob())
|
|
.then((blob) => {
|
|
const url = window.URL.createObjectURL(blob)
|
|
const a = document.createElement("a")
|
|
a.href = url
|
|
a.download = filePath
|
|
a.click()
|
|
})
|
|
}
|
|
|
|
return (
|
|
<div>
|
|
<h1>Download File</h1>
|
|
<input
|
|
type="text"
|
|
value={filePath}
|
|
onChange={(e) => setFilePath(e.target.value)}
|
|
placeholder="Enter file path"
|
|
/>
|
|
<button onClick={handleDownload}>Download</button>
|
|
</div>
|
|
)
|
|
}
|
|
```
|
|
#### 공격 시나리오
|
|
|
|
1. **공격자의 목표**: `filePath`를 조작하여 중요한 파일(예: `admin/config.json`)을 삭제하기 위한 CSRF 공격 수행.
|
|
2. **CSPT 악용**:
|
|
- **악의적인 입력**: 공격자는 `filePath`를 조작한 URL을 생성합니다. 예: `../deleteFile/config.json`.
|
|
- **결과 API 호출**: 클라이언트 측 코드가 `/api/files/../deleteFile/config.json`에 요청을 보냅니다.
|
|
- **서버의 처리**: 서버가 `filePath`를 검증하지 않으면 요청을 처리하여 민감한 파일을 삭제하거나 노출할 수 있습니다.
|
|
3. **CSRF 실행**:
|
|
- **조작된 링크**: 공격자는 피해자에게 링크를 보내거나 조작된 `filePath`로 다운로드 요청을 트리거하는 악성 스크립트를 삽입합니다.
|
|
- **결과**: 피해자는 알지 못하게 작업을 실행하여 무단 파일 접근 또는 삭제로 이어집니다.
|
|
|
|
#### 왜 취약한가
|
|
|
|
- **입력 검증 부족**: 클라이언트 측에서 임의의 `filePath` 입력을 허용하여 경로 탐색을 가능하게 합니다.
|
|
- **클라이언트 입력 신뢰**: 서버 측 API가 `filePath`를 신뢰하고 정화 없이 처리합니다.
|
|
- **잠재적 API 작업**: API 엔드포인트가 상태 변경 작업(예: 파일 삭제, 수정)을 수행하는 경우 CSPT를 통해 악용될 수 있습니다.
|
|
|
|
</details>
|
|
|
|
## Next.js의 서버 측
|
|
|
|
### 서버 측 렌더링 (SSR)
|
|
|
|
페이지는 각 요청 시 서버에서 렌더링되어 사용자가 완전히 렌더링된 HTML을 받도록 보장합니다. 이 경우 요청을 처리하기 위해 자체 사용자 정의 서버를 생성해야 합니다.
|
|
|
|
**사용 사례:**
|
|
|
|
- 자주 변경되는 동적 콘텐츠.
|
|
- 검색 엔진이 완전히 렌더링된 페이지를 크롤링할 수 있으므로 SEO 최적화.
|
|
|
|
**구현:**
|
|
```jsx
|
|
// pages/index.js
|
|
export async function getServerSideProps(context) {
|
|
const res = await fetch("https://api.example.com/data")
|
|
const data = await res.json()
|
|
return { props: { data } }
|
|
}
|
|
|
|
function HomePage({ data }) {
|
|
return <div>{data.title}</div>
|
|
}
|
|
|
|
export default HomePage
|
|
```
|
|
### 정적 사이트 생성 (SSG)
|
|
|
|
페이지는 빌드 시간에 미리 렌더링되어 더 빠른 로드 시간과 감소된 서버 부하를 제공합니다.
|
|
|
|
**사용 사례:**
|
|
|
|
- 자주 변경되지 않는 콘텐츠.
|
|
- 블로그, 문서, 마케팅 페이지.
|
|
|
|
**구현:**
|
|
```jsx
|
|
// pages/index.js
|
|
export async function getStaticProps() {
|
|
const res = await fetch("https://api.example.com/data")
|
|
const data = await res.json()
|
|
return { props: { data }, revalidate: 60 } // Revalidate every 60 seconds
|
|
}
|
|
|
|
function HomePage({ data }) {
|
|
return <div>{data.title}</div>
|
|
}
|
|
|
|
export default HomePage
|
|
```
|
|
### Serverless Functions (API Routes)
|
|
|
|
Next.js는 서버리스 함수로 API 엔드포인트를 생성할 수 있습니다. 이러한 함수는 전용 서버 없이 필요에 따라 실행됩니다.
|
|
|
|
**Use Cases:**
|
|
|
|
- 양식 제출 처리.
|
|
- 데이터베이스와 상호작용.
|
|
- 데이터 처리 또는 서드파티 API와 통합.
|
|
|
|
**Implementation:**
|
|
|
|
Next.js 13에서 `app` 디렉토리가 도입되면서 라우팅 및 API 처리가 더 유연하고 강력해졌습니다. 이 현대적인 접근 방식은 파일 기반 라우팅 시스템과 밀접하게 일치하지만 서버 및 클라이언트 구성 요소에 대한 지원을 포함한 향상된 기능을 도입합니다.
|
|
|
|
#### Basic Route Handler
|
|
|
|
**File Structure:**
|
|
```go
|
|
my-nextjs-app/
|
|
├── app/
|
|
│ └── api/
|
|
│ └── hello/
|
|
│ └── route.js
|
|
├── package.json
|
|
└── ...
|
|
```
|
|
**구현:**
|
|
```javascript
|
|
// app/api/hello/route.js
|
|
|
|
export async function POST(request) {
|
|
return new Response(JSON.stringify({ message: "Hello from App Router!" }), {
|
|
status: 200,
|
|
headers: { "Content-Type": "application/json" },
|
|
})
|
|
}
|
|
|
|
// Client-side fetch to access the API endpoint
|
|
fetch("/api/submit", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ name: "John Doe" }),
|
|
})
|
|
.then((res) => res.json())
|
|
.then((data) => console.log(data))
|
|
```
|
|
**설명:**
|
|
|
|
- **위치:** API 경로는 `app/api/` 디렉토리 아래에 배치됩니다.
|
|
- **파일 이름 지정:** 각 API 엔드포인트는 `route.js` 또는 `route.ts` 파일이 포함된 자체 폴더에 위치합니다.
|
|
- **내보낸 함수:** 단일 기본 내보내기 대신 특정 HTTP 메서드 함수(예: `GET`, `POST`)가 내보내집니다.
|
|
- **응답 처리:** `Response` 생성자를 사용하여 응답을 반환하며, 헤더 및 상태 코드에 대한 더 많은 제어를 허용합니다.
|
|
|
|
#### 다른 경로 및 메서드 처리 방법:
|
|
|
|
<details>
|
|
|
|
<summary>특정 HTTP 메서드 처리</summary>
|
|
|
|
Next.js 13+는 동일한 `route.js` 또는 `route.ts` 파일 내에서 특정 HTTP 메서드에 대한 핸들러를 정의할 수 있도록 하여 더 명확하고 조직적인 코드를 촉진합니다.
|
|
|
|
**예:**
|
|
```javascript
|
|
// app/api/users/[id]/route.js
|
|
|
|
export async function GET(request, { params }) {
|
|
const { id } = params
|
|
// Fetch user data based on 'id'
|
|
return new Response(JSON.stringify({ userId: id, name: "Jane Doe" }), {
|
|
status: 200,
|
|
headers: { "Content-Type": "application/json" },
|
|
})
|
|
}
|
|
|
|
export async function PUT(request, { params }) {
|
|
const { id } = params
|
|
// Update user data based on 'id'
|
|
return new Response(JSON.stringify({ message: `User ${id} updated.` }), {
|
|
status: 200,
|
|
headers: { "Content-Type": "application/json" },
|
|
})
|
|
}
|
|
|
|
export async function DELETE(request, { params }) {
|
|
const { id } = params
|
|
// Delete user based on 'id'
|
|
return new Response(JSON.stringify({ message: `User ${id} deleted.` }), {
|
|
status: 200,
|
|
headers: { "Content-Type": "application/json" },
|
|
})
|
|
}
|
|
```
|
|
**설명:**
|
|
|
|
- **다중 내보내기:** 각 HTTP 메서드(`GET`, `PUT`, `DELETE`)는 고유한 내보내기 함수를 가지고 있습니다.
|
|
- **매개변수:** 두 번째 인수는 `params`를 통해 경로 매개변수에 접근할 수 있게 합니다.
|
|
- **향상된 응답:** 응답 객체에 대한 더 큰 제어를 제공하여 정확한 헤더 및 상태 코드 관리를 가능하게 합니다.
|
|
|
|
</details>
|
|
|
|
<details>
|
|
|
|
<summary>Catch-All 및 중첩 경로</summary>
|
|
|
|
Next.js 13+는 catch-all 경로 및 중첩 API 경로와 같은 고급 라우팅 기능을 지원하여 더 동적이고 확장 가능한 API 구조를 허용합니다.
|
|
|
|
**Catch-All 경로 예:**
|
|
```javascript
|
|
// app/api/[...slug]/route.js
|
|
|
|
export async function GET(request, { params }) {
|
|
const { slug } = params
|
|
// Handle dynamic nested routes
|
|
return new Response(JSON.stringify({ slug }), {
|
|
status: 200,
|
|
headers: { "Content-Type": "application/json" },
|
|
})
|
|
}
|
|
```
|
|
**설명:**
|
|
|
|
- **구문:** `[...]`는 모든 중첩 경로를 포착하는 catch-all 세그먼트를 나타냅니다.
|
|
- **용도:** 다양한 경로 깊이나 동적 세그먼트를 처리해야 하는 API에 유용합니다.
|
|
|
|
**중첩 경로 예:**
|
|
```javascript
|
|
// app/api/posts/[postId]/comments/[commentId]/route.js
|
|
|
|
export async function GET(request, { params }) {
|
|
const { postId, commentId } = params
|
|
// Fetch specific comment for a post
|
|
return new Response(
|
|
JSON.stringify({ postId, commentId, comment: "Great post!" }),
|
|
{
|
|
status: 200,
|
|
headers: { "Content-Type": "application/json" },
|
|
}
|
|
)
|
|
}
|
|
```
|
|
**설명:**
|
|
|
|
- **깊은 중첩:** 리소스 관계를 반영하는 계층적 API 구조를 허용합니다.
|
|
- **매개변수 접근:** `params` 객체를 통해 여러 경로 매개변수에 쉽게 접근할 수 있습니다.
|
|
|
|
</details>
|
|
|
|
<details>
|
|
|
|
<summary>Next.js 12 및 이전 버전에서 API 경로 처리</summary>
|
|
|
|
## `pages` 디렉토리의 API 경로 (Next.js 12 및 이전 버전)
|
|
|
|
Next.js 13이 `app` 디렉토리를 도입하고 라우팅 기능을 향상시키기 전, API 경로는 주로 `pages` 디렉토리 내에서 정의되었습니다. 이 접근 방식은 여전히 널리 사용되며 Next.js 12 및 이전 버전에서 지원됩니다.
|
|
|
|
#### 기본 API 경로
|
|
|
|
**파일 구조:**
|
|
```go
|
|
goCopy codemy-nextjs-app/
|
|
├── pages/
|
|
│ └── api/
|
|
│ └── hello.js
|
|
├── package.json
|
|
└── ...
|
|
```
|
|
**구현:**
|
|
```javascript
|
|
javascriptCopy code// pages/api/hello.js
|
|
|
|
export default function handler(req, res) {
|
|
res.status(200).json({ message: 'Hello, World!' });
|
|
}
|
|
```
|
|
**설명:**
|
|
|
|
- **위치:** API 경로는 `pages/api/` 디렉토리 아래에 있습니다.
|
|
- **내보내기:** 핸들러 함수를 정의하려면 `export default`를 사용합니다.
|
|
- **함수 시그니처:** 핸들러는 `req` (HTTP 요청) 및 `res` (HTTP 응답) 객체를 받습니다.
|
|
- **라우팅:** 파일 이름(`hello.js`)은 엔드포인트 `/api/hello`에 매핑됩니다.
|
|
|
|
#### 동적 API 경로
|
|
|
|
**파일 구조:**
|
|
```bash
|
|
bashCopy codemy-nextjs-app/
|
|
├── pages/
|
|
│ └── api/
|
|
│ └── users/
|
|
│ └── [id].js
|
|
├── package.json
|
|
└── ...
|
|
```
|
|
**구현:**
|
|
```javascript
|
|
javascriptCopy code// pages/api/users/[id].js
|
|
|
|
export default function handler(req, res) {
|
|
const {
|
|
query: { id },
|
|
method,
|
|
} = req;
|
|
|
|
switch (method) {
|
|
case 'GET':
|
|
// Fetch user data based on 'id'
|
|
res.status(200).json({ userId: id, name: 'John Doe' });
|
|
break;
|
|
case 'PUT':
|
|
// Update user data based on 'id'
|
|
res.status(200).json({ message: `User ${id} updated.` });
|
|
break;
|
|
case 'DELETE':
|
|
// Delete user based on 'id'
|
|
res.status(200).json({ message: `User ${id} deleted.` });
|
|
break;
|
|
default:
|
|
res.setHeader('Allow', ['GET', 'PUT', 'DELETE']);
|
|
res.status(405).end(`Method ${method} Not Allowed`);
|
|
}
|
|
}
|
|
```
|
|
**설명:**
|
|
|
|
- **동적 세그먼트:** 대괄호(`[id].js`)는 동적 경로 세그먼트를 나타냅니다.
|
|
- **매개변수 접근:** `req.query.id`를 사용하여 동적 매개변수에 접근합니다.
|
|
- **메서드 처리:** 조건 논리를 활용하여 다양한 HTTP 메서드(`GET`, `PUT`, `DELETE` 등)를 처리합니다.
|
|
|
|
#### 다양한 HTTP 메서드 처리
|
|
|
|
기본 API 경로 예제는 단일 함수 내에서 모든 HTTP 메서드를 처리하지만, 각 메서드를 명시적으로 처리하도록 코드를 구조화하여 더 나은 명확성과 유지 관리를 할 수 있습니다.
|
|
|
|
**예제:**
|
|
```javascript
|
|
javascriptCopy code// pages/api/posts.js
|
|
|
|
export default async function handler(req, res) {
|
|
const { method } = req;
|
|
|
|
switch (method) {
|
|
case 'GET':
|
|
// Handle GET request
|
|
res.status(200).json({ message: 'Fetching posts.' });
|
|
break;
|
|
case 'POST':
|
|
// Handle POST request
|
|
res.status(201).json({ message: 'Post created.' });
|
|
break;
|
|
default:
|
|
res.setHeader('Allow', ['GET', 'POST']);
|
|
res.status(405).end(`Method ${method} Not Allowed`);
|
|
}
|
|
}
|
|
```
|
|
**모범 사례:**
|
|
|
|
- **관심사의 분리:** 서로 다른 HTTP 메서드에 대한 로직을 명확하게 분리합니다.
|
|
- **응답 일관성:** 클라이언트 측 처리를 용이하게 하기 위해 일관된 응답 구조를 보장합니다.
|
|
- **오류 처리:** 지원되지 않는 메서드와 예기치 않은 오류를 우아하게 처리합니다.
|
|
|
|
</details>
|
|
|
|
### CORS 구성
|
|
|
|
어떤 출처가 API 경로에 접근할 수 있는지 제어하여 교차 출처 리소스 공유(CORS) 취약점을 완화합니다.
|
|
|
|
**잘못된 구성 예:**
|
|
```javascript
|
|
// app/api/data/route.js
|
|
|
|
export async function GET(request) {
|
|
return new Response(JSON.stringify({ data: "Public Data" }), {
|
|
status: 200,
|
|
headers: {
|
|
"Access-Control-Allow-Origin": "*", // Allows any origin
|
|
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE",
|
|
},
|
|
})
|
|
}
|
|
```
|
|
**CORS는 또한 **`middleware.ts`** 파일 내의 모든 API 경로에서 구성할 수 있습니다:**
|
|
```javascript
|
|
// app/middleware.ts
|
|
|
|
import { NextResponse } from "next/server"
|
|
import type { NextRequest } from "next/server"
|
|
|
|
export function middleware(request: NextRequest) {
|
|
const allowedOrigins = [
|
|
"https://yourdomain.com",
|
|
"https://sub.yourdomain.com",
|
|
]
|
|
const origin = request.headers.get("Origin")
|
|
|
|
const response = NextResponse.next()
|
|
|
|
if (allowedOrigins.includes(origin || "")) {
|
|
response.headers.set("Access-Control-Allow-Origin", origin || "")
|
|
response.headers.set(
|
|
"Access-Control-Allow-Methods",
|
|
"GET, POST, PUT, DELETE, OPTIONS"
|
|
)
|
|
response.headers.set(
|
|
"Access-Control-Allow-Headers",
|
|
"Content-Type, Authorization"
|
|
)
|
|
// If credentials are needed:
|
|
// response.headers.set('Access-Control-Allow-Credentials', 'true');
|
|
}
|
|
|
|
// Handle preflight requests
|
|
if (request.method === "OPTIONS") {
|
|
return new Response(null, {
|
|
status: 204,
|
|
headers: response.headers,
|
|
})
|
|
}
|
|
|
|
return response
|
|
}
|
|
|
|
export const config = {
|
|
matcher: "/api/:path*", // Apply to all API routes
|
|
}
|
|
```
|
|
**문제:**
|
|
|
|
- **`Access-Control-Allow-Origin: '*'`:** 모든 웹사이트가 API에 접근할 수 있도록 허용하여, 악의적인 사이트가 제한 없이 API와 상호작용할 수 있게 할 수 있습니다.
|
|
- **광범위한 메서드 허용:** 모든 메서드를 허용하면 공격자가 원하지 않는 작업을 수행할 수 있습니다.
|
|
|
|
**공격자가 이를 악용하는 방법:**
|
|
|
|
공격자는 악의적인 웹사이트를 만들어 API에 요청을 하여, 데이터 검색, 데이터 조작 또는 인증된 사용자를 대신하여 원하지 않는 작업을 트리거하는 기능을 악용할 수 있습니다.
|
|
|
|
{{#ref}}
|
|
../../pentesting-web/cors-bypass.md
|
|
{{#endref}}
|
|
|
|
### 클라이언트 측의 서버 코드 노출
|
|
|
|
**서버에서 사용되는 코드를 클라이언트 측에서 노출되고 사용되는 코드에서도 쉽게 사용할 수 있습니다.** 클라이언트 측에서 파일이 노출되지 않도록 보장하는 가장 좋은 방법은 파일의 시작 부분에 이 import를 사용하는 것입니다:
|
|
```js
|
|
import "server-only"
|
|
```
|
|
## 주요 파일 및 역할
|
|
|
|
### `middleware.ts` / `middleware.js`
|
|
|
|
**위치:** 프로젝트의 루트 또는 `src/` 내.
|
|
|
|
**목적:** 요청이 처리되기 전에 서버 측 서버리스 함수에서 코드를 실행하여 인증, 리디렉션 또는 응답 수정과 같은 작업을 수행할 수 있게 합니다.
|
|
|
|
**실행 흐름:**
|
|
|
|
1. **수신 요청:** 미들웨어가 요청을 가로챕니다.
|
|
2. **처리:** 요청에 따라 작업을 수행합니다 (예: 인증 확인).
|
|
3. **응답 수정:** 응답을 변경하거나 다음 핸들러로 제어를 전달할 수 있습니다.
|
|
|
|
**예시 사용 사례:**
|
|
|
|
- 인증되지 않은 사용자 리디렉션.
|
|
- 사용자 정의 헤더 추가.
|
|
- 요청 로깅.
|
|
|
|
**샘플 구성:**
|
|
```typescript
|
|
// middleware.ts
|
|
import { NextResponse } from "next/server"
|
|
import type { NextRequest } from "next/server"
|
|
|
|
export function middleware(req: NextRequest) {
|
|
const url = req.nextUrl.clone()
|
|
if (!req.cookies.has("token")) {
|
|
url.pathname = "/login"
|
|
return NextResponse.redirect(url)
|
|
}
|
|
return NextResponse.next()
|
|
}
|
|
|
|
export const config = {
|
|
matcher: ["/protected/:path*"],
|
|
}
|
|
```
|
|
### `next.config.js`
|
|
|
|
**위치:** 프로젝트의 루트.
|
|
|
|
**목적:** Next.js 동작을 구성하고, 기능을 활성화 또는 비활성화하며, webpack 구성을 사용자 정의하고, 환경 변수를 설정하고, 여러 보안 기능을 구성합니다.
|
|
|
|
**주요 보안 구성:**
|
|
|
|
<details>
|
|
|
|
<summary>보안 헤더</summary>
|
|
|
|
보안 헤더는 브라우저에 콘텐츠를 처리하는 방법을 지시하여 애플리케이션의 보안을 강화합니다. 이들은 교차 사이트 스크립팅(XSS), 클릭재킹, MIME 타입 스니핑과 같은 다양한 공격을 완화하는 데 도움을 줍니다:
|
|
|
|
- 콘텐츠 보안 정책 (CSP)
|
|
- X-Frame-Options
|
|
- X-Content-Type-Options
|
|
- 엄격한 전송 보안 (HSTS)
|
|
- 리퍼러 정책
|
|
|
|
**예시:**
|
|
```javascript
|
|
// next.config.js
|
|
|
|
module.exports = {
|
|
async headers() {
|
|
return [
|
|
{
|
|
source: "/(.*)", // Apply to all routes
|
|
headers: [
|
|
{
|
|
key: "X-Frame-Options",
|
|
value: "DENY",
|
|
},
|
|
{
|
|
key: "Content-Security-Policy",
|
|
value:
|
|
"default-src *; script-src 'self' 'unsafe-inline' 'unsafe-eval';",
|
|
},
|
|
{
|
|
key: "X-Content-Type-Options",
|
|
value: "nosniff",
|
|
},
|
|
{
|
|
key: "Strict-Transport-Security",
|
|
value: "max-age=63072000; includeSubDomains; preload", // Enforces HTTPS
|
|
},
|
|
{
|
|
key: "Referrer-Policy",
|
|
value: "no-referrer", // Completely hides referrer
|
|
},
|
|
// Additional headers...
|
|
],
|
|
},
|
|
]
|
|
},
|
|
}
|
|
```
|
|
</details>
|
|
|
|
<details>
|
|
|
|
<summary>이미지 최적화 설정</summary>
|
|
|
|
Next.js는 성능을 위해 이미지를 최적화하지만, 잘못된 구성은 신뢰할 수 없는 소스가 악성 콘텐츠를 주입할 수 있는 보안 취약점을 초래할 수 있습니다.
|
|
|
|
**잘못된 구성 예:**
|
|
```javascript
|
|
// next.config.js
|
|
|
|
module.exports = {
|
|
images: {
|
|
domains: ["*"], // Allows images from any domain
|
|
},
|
|
}
|
|
```
|
|
**문제:**
|
|
|
|
- **`'*'`:** 신뢰할 수 없거나 악의적인 도메인을 포함하여 모든 외부 소스에서 이미지를 로드할 수 있도록 허용합니다. 공격자는 악성 페이로드나 사용자를 오도하는 콘텐츠가 포함된 이미지를 호스팅할 수 있습니다.
|
|
- 또 다른 문제는 **누구나 이미지를 업로드할 수 있는 도메인**을 허용하는 것입니다 (예: `raw.githubusercontent.com`)
|
|
|
|
**공격자가 이를 악용하는 방법:**
|
|
|
|
악성 소스에서 이미지를 주입함으로써 공격자는 피싱 공격을 수행하거나, 오해의 소지가 있는 정보를 표시하거나, 이미지 렌더링 라이브러리의 취약점을 악용할 수 있습니다.
|
|
|
|
</details>
|
|
|
|
<details>
|
|
|
|
<summary>환경 변수 노출</summary>
|
|
|
|
API 키 및 데이터베이스 자격 증명과 같은 민감한 정보를 클라이언트에 노출하지 않고 안전하게 관리합니다.
|
|
|
|
#### a. 민감한 변수 노출
|
|
|
|
**잘못된 구성 예:**
|
|
```javascript
|
|
// next.config.js
|
|
|
|
module.exports = {
|
|
env: {
|
|
SECRET_API_KEY: process.env.SECRET_API_KEY, // Exposed to the client
|
|
NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL, // Correctly prefixed for client
|
|
},
|
|
}
|
|
```
|
|
**문제:**
|
|
|
|
- **`SECRET_API_KEY`:** `NEXT_PUBLIC_` 접두사가 없으면 Next.js는 변수를 클라이언트에 노출하지 않습니다. 그러나 실수로 접두사가 붙으면 (예: `NEXT_PUBLIC_SECRET_API_KEY`), 클라이언트 측에서 접근할 수 있게 됩니다.
|
|
|
|
**공격자가 이를 악용하는 방법:**
|
|
|
|
민감한 변수가 클라이언트에 노출되면, 공격자는 클라이언트 측 코드나 네트워크 요청을 검사하여 이를 검색하고, API, 데이터베이스 또는 기타 서비스에 대한 무단 접근을 얻을 수 있습니다.
|
|
|
|
</details>
|
|
|
|
<details>
|
|
|
|
<summary>리다이렉트</summary>
|
|
|
|
응용 프로그램 내에서 URL 리다이렉션 및 재작성 관리를 통해 사용자가 적절하게 안내되도록 하여 열린 리다이렉트 취약점이 발생하지 않도록 합니다.
|
|
|
|
#### a. 열린 리다이렉트 취약점
|
|
|
|
**잘못된 구성 예:**
|
|
```javascript
|
|
// next.config.js
|
|
|
|
module.exports = {
|
|
async redirects() {
|
|
return [
|
|
{
|
|
source: "/redirect",
|
|
destination: (req) => req.query.url, // Dynamically redirects based on query parameter
|
|
permanent: false,
|
|
},
|
|
]
|
|
},
|
|
}
|
|
```
|
|
**문제:**
|
|
|
|
- **동적 목적지:** 사용자가 임의의 URL을 지정할 수 있어 오픈 리디렉션 공격을 가능하게 합니다.
|
|
- **사용자 입력 신뢰:** 검증 없이 사용자가 제공한 URL로 리디렉션하면 피싱, 악성 소프트웨어 배포 또는 자격 증명 도용으로 이어질 수 있습니다.
|
|
|
|
**공격자가 이를 악용하는 방법:**
|
|
|
|
공격자는 귀하의 도메인에서 시작된 것처럼 보이는 URL을 만들어 사용자를 악성 사이트로 리디렉션할 수 있습니다. 예를 들어:
|
|
```bash
|
|
https://yourdomain.com/redirect?url=https://malicious-site.com
|
|
```
|
|
사용자가 원래 도메인을 신뢰하면 의도치 않게 해로운 웹사이트로 이동할 수 있습니다.
|
|
|
|
</details>
|
|
|
|
<details>
|
|
|
|
<summary>Webpack 구성</summary>
|
|
|
|
Next.js 애플리케이션에 대한 Webpack 구성을 사용자 정의하십시오. 주의하지 않으면 보안 취약점을 초래할 수 있습니다.
|
|
|
|
#### a. 민감한 모듈 노출
|
|
|
|
**잘못된 구성 예:**
|
|
```javascript
|
|
// next.config.js
|
|
|
|
module.exports = {
|
|
webpack: (config, { isServer }) => {
|
|
if (!isServer) {
|
|
config.resolve.alias["@sensitive"] = path.join(__dirname, "secret-folder")
|
|
}
|
|
return config
|
|
},
|
|
}
|
|
```
|
|
**문제:**
|
|
|
|
- **민감한 경로 노출:** 민감한 디렉토리를 별칭 처리하고 클라이언트 측 접근을 허용하면 기밀 정보가 유출될 수 있습니다.
|
|
- **비밀 번들링:** 민감한 파일이 클라이언트를 위해 번들링되면, 그 내용이 소스 맵이나 클라이언트 측 코드를 검사하여 접근 가능해집니다.
|
|
|
|
**공격자가 이를 악용하는 방법:**
|
|
|
|
공격자는 애플리케이션의 디렉토리 구조에 접근하거나 재구성할 수 있으며, 잠재적으로 민감한 파일이나 데이터를 찾고 악용할 수 있습니다.
|
|
|
|
</details>
|
|
|
|
### `pages/_app.js` 및 `pages/_document.js`
|
|
|
|
#### **`pages/_app.js`**
|
|
|
|
**목적:** 기본 App 구성 요소를 재정의하여 전역 상태, 스타일 및 레이아웃 구성 요소를 허용합니다.
|
|
|
|
**사용 사례:**
|
|
|
|
- 전역 CSS 주입.
|
|
- 레이아웃 래퍼 추가.
|
|
- 상태 관리 라이브러리 통합.
|
|
|
|
**예시:**
|
|
```jsx
|
|
// pages/_app.js
|
|
import "../styles/globals.css"
|
|
|
|
function MyApp({ Component, pageProps }) {
|
|
return <Component {...pageProps} />
|
|
}
|
|
|
|
export default MyApp
|
|
```
|
|
#### **`pages/_document.js`**
|
|
|
|
**목적:** 기본 Document를 재정의하여 HTML 및 Body 태그의 사용자 지정을 가능하게 합니다.
|
|
|
|
**사용 사례:**
|
|
|
|
- `<html>` 또는 `<body>` 태그 수정.
|
|
- 메타 태그 또는 사용자 정의 스크립트 추가.
|
|
- 서드파티 글꼴 통합.
|
|
|
|
**예시:**
|
|
```jsx
|
|
// pages/_document.js
|
|
import Document, { Html, Head, Main, NextScript } from "next/document"
|
|
|
|
class MyDocument extends Document {
|
|
render() {
|
|
return (
|
|
<Html lang="en">
|
|
<Head>{/* Custom fonts or meta tags */}</Head>
|
|
<body>
|
|
<Main />
|
|
<NextScript />
|
|
</body>
|
|
</Html>
|
|
)
|
|
}
|
|
}
|
|
|
|
export default MyDocument
|
|
```
|
|
### 커스텀 서버 (선택 사항)
|
|
|
|
**목적:** Next.js는 내장 서버를 제공하지만, 커스텀 라우팅이나 기존 백엔드 서비스와의 통합과 같은 고급 사용 사례를 위해 커스텀 서버를 만들 수 있습니다.
|
|
|
|
**참고:** 커스텀 서버를 사용하면 Next.js의 내장 서버에 최적화된 Vercel과 같은 플랫폼에서 배포 옵션이 제한될 수 있습니다.
|
|
|
|
**예시:**
|
|
```javascript
|
|
// server.js
|
|
const express = require("express")
|
|
const next = require("next")
|
|
|
|
const dev = process.env.NODE_ENV !== "production"
|
|
const app = next({ dev })
|
|
const handle = app.getRequestHandler()
|
|
|
|
app.prepare().then(() => {
|
|
const server = express()
|
|
|
|
// Custom route
|
|
server.get("/a", (req, res) => {
|
|
return app.render(req, res, "/a")
|
|
})
|
|
|
|
// Default handler
|
|
server.all("*", (req, res) => {
|
|
return handle(req, res)
|
|
})
|
|
|
|
server.listen(3000, (err) => {
|
|
if (err) throw err
|
|
console.log("> Ready on http://localhost:3000")
|
|
})
|
|
})
|
|
```
|
|
---
|
|
|
|
## 추가적인 아키텍처 및 보안 고려사항
|
|
|
|
### 환경 변수 및 구성
|
|
|
|
**목적:** 코드베이스 외부에서 민감한 정보 및 구성 설정을 관리합니다.
|
|
|
|
**모범 사례:**
|
|
|
|
- **`.env` 파일 사용:** API 키와 같은 변수를 `.env.local`에 저장합니다 (버전 관리에서 제외).
|
|
- **변수를 안전하게 접근:** `process.env.VARIABLE_NAME`을 사용하여 환경 변수에 접근합니다.
|
|
- **클라이언트에 비밀 노출 금지:** 민감한 변수는 서버 측에서만 사용되도록 합니다.
|
|
|
|
**예시:**
|
|
```javascript
|
|
// next.config.js
|
|
module.exports = {
|
|
env: {
|
|
API_KEY: process.env.API_KEY, // Accessible on both client and server
|
|
SECRET_KEY: process.env.SECRET_KEY, // Be cautious if accessible on the client
|
|
},
|
|
}
|
|
```
|
|
**참고:** 변수를 서버 측 전용으로 제한하려면 `env` 객체에서 생략하거나 클라이언트 노출을 위해 `NEXT_PUBLIC_`로 접두사를 붙이십시오.
|
|
|
|
### 인증 및 권한 부여
|
|
|
|
**접근 방식:**
|
|
|
|
- **세션 기반 인증:** 쿠키를 사용하여 사용자 세션을 관리합니다.
|
|
- **토큰 기반 인증:** 상태 비저장 인증을 위해 JWT를 구현합니다.
|
|
- **타사 제공업체:** `next-auth`와 같은 라이브러리를 사용하여 OAuth 제공업체(예: Google, GitHub)와 통합합니다.
|
|
|
|
**보안 관행:**
|
|
|
|
- **보안 쿠키:** `HttpOnly`, `Secure`, 및 `SameSite` 속성을 설정합니다.
|
|
- **비밀번호 해싱:** 비밀번호를 저장하기 전에 항상 해싱합니다.
|
|
- **입력 검증:** 입력을 검증하고 정리하여 주입 공격을 방지합니다.
|
|
|
|
**예시:**
|
|
```javascript
|
|
// pages/api/login.js
|
|
import { sign } from "jsonwebtoken"
|
|
import { serialize } from "cookie"
|
|
|
|
export default async function handler(req, res) {
|
|
const { username, password } = req.body
|
|
|
|
// Validate user credentials
|
|
if (username === "admin" && password === "password") {
|
|
const token = sign({ username }, process.env.JWT_SECRET, {
|
|
expiresIn: "1h",
|
|
})
|
|
res.setHeader(
|
|
"Set-Cookie",
|
|
serialize("auth", token, {
|
|
path: "/",
|
|
httpOnly: true,
|
|
secure: true,
|
|
sameSite: "strict",
|
|
})
|
|
)
|
|
res.status(200).json({ message: "Logged in" })
|
|
} else {
|
|
res.status(401).json({ error: "Invalid credentials" })
|
|
}
|
|
}
|
|
```
|
|
### 성능 최적화
|
|
|
|
**전략:**
|
|
|
|
- **이미지 최적화:** Next.js의 `next/image` 컴포넌트를 사용하여 자동 이미지 최적화를 수행합니다.
|
|
- **코드 분할:** 동적 임포트를 활용하여 코드를 분할하고 초기 로드 시간을 줄입니다.
|
|
- **캐싱:** API 응답 및 정적 자산에 대한 캐싱 전략을 구현합니다.
|
|
- **지연 로딩:** 필요할 때만 컴포넌트나 자산을 로드합니다.
|
|
|
|
**예시:**
|
|
```jsx
|
|
// Dynamic Import with Code Splitting
|
|
import dynamic from "next/dynamic"
|
|
|
|
const HeavyComponent = dynamic(() => import("../components/HeavyComponent"), {
|
|
loading: () => <p>Loading...</p>,
|
|
})
|
|
```
|
|
{{#include ../../banners/hacktricks-training.md}}
|