2025. 11. 27. 20:22ใTrouble Shooting & Issues/Linkiving

๋ชฉ์ฐจ
1. ์ ์ฒด ๊ตฌ์กฐ
2. api.ts : url ์์
3. handlerServerError.ts : ์๋ฒ ์๋ฌ ํธ๋ค๋ฌ
4. safeFetch.ts : ์์ ํ fetch
5. articles์ route.ts
1. ์ ์ฒด ๊ตฌ์กฐ
์ฐ์ articles์์ ๋ค๋ค์ผ ํ๋ ๋ฐ์ดํฐ๋ค์ ๋ค์๊ณผ ๊ฐ๋ค.

Next.js์ app router์์๋ ๋ณด์ด๋ ์ค์จ๊ฑฐ ๊ตฌ์กฐ๋ฅผ ๊ทธ๋๋ก ํด๋ ๊ตฌ์กฐ๋ก ๋ง๋ค๋ฉด ๋๋ค.
๋ฌผ๋ก ์ด ๋ ์ด ํด๋๋ค์ ๋ชจ๋ ๊ฐ๊ฐ์ route.ts๋ฅผ ํฌํจํ๋ค.

ํด๋ ๊ตฌ์กฐ๋ฅผ ์ก์ ํ์๋ ๊ฐ ํํธ์์ ํ์ํ ํจ์๋ฅผ ์์ฑํ๋ค.
articles
• export async function POST() { ... }
• export async function GET() { ... }
[articleId]
• export async function GET() { ... }
• export async function PATCH() { ... }
• export async function DELETE() { ... }
like
• export async function POST() { ... }
• export async function DELETE() { ... }
์ด ์ค articles์ POST๋ก ์ ์ฒด ๊ตฌ์กฐ๋ฅผ ๋ณด๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.
import { NextRequest, NextResponse } from 'next/server';
import { API } from '@/constants/api';
import { handlerServerError } from '@/utils/handlerServerError';
import { safeFetch } from '@/utils/safeFetch';
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const { image, title, content } = body;
const payload = {
image,
title,
content,
};
const data = await safeFetch(`${API.ARTICLES}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
});
return NextResponse.json({
message: 'post article',
data: data,
});
} catch (err: unknown) {
return handlerServerError(err, 'Failed to post articles');
}
}
์ฐ์ importํ๊ณ ์๋ API, handlerServerError, safeFetch๋ถํฐ ๋ณด์.
2. api.ts : url ์์
constants/api.ts์์๋ url ์์๋ฅผ ์ ์ํ๊ณ ์๋ค.
const BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL;
export const API = {
BASE: BASE_URL,
USERS: `${BASE_URL}/users/`,
PROFILE: `${BASE_URL}/profiles/`,
NOTIFICATION: `${BASE_URL}/notifications/`,
IMAGE: `${BASE_URL}/images/upload/`,
COMMENT: `${BASE_URL}/comments/`,
AUTH: `${BASE_URL}/auth/`,
ARTICLES: `${BASE_URL}/articles/`,
};
.env ํ์ผ์ ์๋ฒ ์ฃผ์๋ฅผ ์์ฑํ์ฌ BASE_URL๋ก ์ฃผ์๊ณ , API ๊ฐ์ฒด๋ฅผ ์ ์ํ์ฌ ์ค์จ๊ฑฐ์์ ํ์ธํ ์ ์๋ url๋ค์ ์ ์ํ๋ค.
3. handlerServerError.ts : ์๋ฒ ์๋ฌ ํธ๋ค๋ฌ
src/utils/handlerServerError.ts์์๋ ์๋ฒ ์๋ฌ๋ฅผ ๋ค๋ฃฌ๋ค.
try๋ฌธ์์ ์๋ฌ๋ฅผ ์ก์์ ๋ ์๋ฌ๋ฅผ ์ฒ๋ฆฌํ๋ ์ฝ๋๊ฐ ๋ฐ๋ณต๋์ด ์์ฑํด๋ดค๋ค.
import { NextResponse } from 'next/server';
export function handlerServerError(err: unknown, msg: string) {
if (err instanceof Error) {
console.error('[ServerError]', err.message); // ์๋ฒ ์ฝ์์ ์ถ๋ ฅ๋๋ ์๋ฌ
return NextResponse.json({ error: msg }, { status: 500 }); // ํด๋ผ์ด์ธํธ์ ์ถ๋ ฅ๋๋ ์๋ฌ
} else {
console.error('[UnknownError]', err);
return NextResponse.json({ error: msg }, { status: 500 });
}
}
console.error๋ ์๋ฒ ์ฝ์์๋ง ์ถ๋ ฅ๋๊ณ , NextResponse๋ ํด๋ผ์ด์ธํธ ์ฝ์์ ์ถ๋ ฅ๋๋ค.
์๋ฒ ์๋ฌ์ ๊ฒฝ์ฐ, ํด๋ผ์ด์ธํธ ์ธก์ ๋ณด์ผ ํ์๊ฐ ์๊ธฐ ๋๋ฌธ์
์๋ฌ ๋ฉ์ธ์ง๋ฅผ ํ์ธํ ์ ์๋ err.message๋ console.error๋ก,
์ง์ ์ง์ ํ ๋ฉ์ธ์ง์ธ msg๋ NextResponse๋ก ๋ณด๋๋ค.
4. safeFetch.ts : ์์ ํ fetch
* fetch๋? : ๋คํธ์ํฌ ์์ฒญ์ ๋ณด๋ด๊ณ ์๋ต์ ๋ฐ๋ ๋ธ๋ผ์ฐ์ /Node API
src/utils/safeFetch.ts๋ fetch ์์ฒญ์ ์์ ํ๊ฒ ์ํํ๊ณ , ์คํจ ์์ ์๋ฒ/ํด๋ผ์ด์ธํธ์ฉ ์๋ฌ ๋ฉ์ธ์ง๋ฅผ ๊ตฌ๋ถํ๋ค.
export async function safeFetch(url: string, options?: RequestInit) {
try {
const res = await fetch(url, options);
// ์์ ํ๊ฒ fetch ์์ฒญ ์ํ: ์๋ต ์ํ ์ฝ๋๊ฐ 200~299(์์ฒญ ์ฑ๊ณต)์ธ์ง ํ์ธ
if (!res.ok) {
throw new Error(`Request failed with status ${res.status}`);
}
const data = await res.json();
return data;
} catch (err: unknown) {
console.error(err instanceof Error ? err.message : err); // ์๋ฒ์ฉ ์๋ฌ ๋ฉ์ธ์ง
throw new Error('Server request failed'); // ํด๋ผ์ด์ธํธ์ฉ ์๋ฌ ๋ฉ์ธ์ง
}
}
์์ฒญ url๊ณผ ์ต์ ์ props๋ก ๋ฐ๋๋ค.
- RequestInit : fetch ํจ์์ ์ต์ ์ ์ค ๋ ์ฐ๋ ํ์ ์ผ๋ก, HTTP ๋ฉ์๋, ํค๋, body ๋ฑ์ ์์ฒญ ์ค์ ์ ๋ด๋ ๊ฐ์ฒด์ด๋ค.
- res.ok : ์๋ต ์ํ ์ฝ๋๊ฐ 200~299(์์ฒญ ์ฑ๊ณต)์ธ์ง ํ์ธํ๋ค.
- ์ฝ๋๊ฐ 200~299 ๋ด ๋ฒ์๋ผ๋ฉด ์์ฒญ์ ์ฑ๊ณตํ๋ค๋ ๊ฑธ ์๋ฏธํ๋ค. ์ดํ ์๋ฌ ๋ฐ์์ ์๋ฒ ์๋ฌ๋ก ์ฒ๋ฆฌ๋๋ค.
- ์ฝ๋๊ฐ 200~299 ์ธ ๋ฒ์๋ผ๋ฉด ์์ฒญ์ด ์คํจํ๋ค๋ ๋ป์ผ๋ก, ํด๋ผ์ด์ธํธ์๊ฒ ์๋ฆฐ๋ค. (404 ๋ฑ)
- data : res.ok๋ฅผ ๋ฌด์ฌํ ํต๊ณผํ๋ฉด ์๋ต์ data์ ๋๊ธฐ๊ณ ๋ฆฌํดํ๋ค. ์ดํ ๋ฐ์ํ๋ ์๋ฌ๋ ์๋ฒ ์๋ฌ๋ก,
์๋ฒ์๊ฒ๋ console.error๋ก ์๋ฒ์ฉ ์๋ฌ ๋ฉ์ธ์ง๋ฅผ ๋ณด์ด๊ณ , ํด๋ผ์ด์ธํธ์๊ฒ๋ Error๋ก ํด๋ผ์ด์ธํธ์ฉ ์๋ฌ ๋ฉ์ธ์ง๋ฅผ ๋ณด์ธ๋ค.
5. articles์ route.ts
๊ทธ๋ผ ๋ค์ articles์ route.ts๋ฅผ ๋ณด์.
POST : articles
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const { image, title, content } = body;
const payload = {
image,
title,
content,
};
const data = await safeFetch(`${API.ARTICLES}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
});
return NextResponse.json({
message: 'post article',
data: data,
});
} catch (err: unknown) {
return handlerServerError(err, 'Failed to post articles');
}
}
๊ฒ์๊ธ์ ๋ฑ๋กํ๋ฉด articles์ POST๋๋ค.
๋์ ์์๋๋ก ๋ณด๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.
- ํด๋ผ์ด์ธํธ๋ก๋ถํฐ ์์ฒญ์ ๋ฐ์ body์ ๋๊น
- body๋ฅผ image, title, content๋ก ๊ฐ์ฒด๊ตฌ์กฐ๋ถํดํจ
- ์๋ฒ์ ๋ฐ์ดํฐ๋ฅผ ๋ณด๋ผ ๊ฐ์ฒด์ธ payload์ image, title, content๋ฅผ ๋ฌถ์ด์ ๋ด์
- API.ARTICLES ์ฃผ์๋ก options์ ๋ฐ๋ผ safeFetchํจ์๋ก fetch๋ฅผ ์งํํ์ฌ ์๋ต์ data์ ์ ๋ฌ
- NextResponse.json()์ ์์ ๋ชจ์์ผ๋ก data๋ฅผ ์ ๋ฌ
- ์ค๊ฐ์ ์๋ฌ ๋ฐ์ ์ handlerServerError()๋ก ์๋ฌ ์ฒ๋ฆฌ (์ด ์๋ฌ๋ค์ ์๋ฒ ์๋ฌ์. ์๋ํ๋ฉด ํด๋ผ์ด์ธํธ ์๋ฌ๋ safeFech์์ ์ก์)
GET : articles
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url);
const page = searchParams.get('page') ?? '1'; // ํ์ด์ง ๋ฒํธ
const pageSize = searchParams.get('pageSize') ?? '10'; // ํ์ด์ง ๋น ๊ฒ์๊ธ ์
const orderBy = searchParams.get('orderBy') ?? 'recent'; // ์ ๋ ฌ ๊ธฐ์ค
const keyword = searchParams.get('keyword') ?? ''; // ๊ฒ์ ํค์๋
const query = new URLSearchParams({
page,
pageSize,
orderBy,
});
if (keyword) query.append('keyword', keyword);
const data = await safeFetch(`${API.ARTICLES}?${query.toString()}`);
return NextResponse.json({
message: 'get article',
data: data,
});
} catch (err: unknown) {
return handlerServerError(err, 'Failed to get articles');
}
}
์๊น POST์์๋ request.json()์ผ๋ก body๋ฅผ ์ป์ด ๊ฐ์ฒด๋ฅผ ๋ฐ์๋ค๋ฉด,
1. ์ด๋ฒ GET์์๋ request.url๋ก๋ถํฐ ํ๋ผ๋ฏธํฐ๋ฅผ ์ฝ์ด์จ๋ค.
payload๋ก ์๋ฒ์ ๋ณด๋ผ ๊ฐ์ฒด๋ฅผ ๊ตฌ์ฑํ๋ค๋ฉด,
2. query๋ก ์๋ฒ์ ์์ฒญํ ๋ฌธ์์ด์ ๊ตฌ์ฑํ๋ค.
3. API.ARTICLES ๋ค์ ๊ตฌ์ฑํ ์ฟผ๋ฆฌ ๋ฌธ์์ด์ ๋ถ์ฌ safeFetch๋ก ์์ฒญ์ ํ๊ณ , ์๋ต์ data์ ๋ด๋๋ค.
4. data๋ฅผ ์์๊ฒ ํด๋ผ์ด์ธํธ์๊ฒ ๋ณด์ฌ์ค๋ค.
6. [articleId]์ route.ts
์ด๋ฒ์๋ articleId๋ฅผ ๋ณด์.
articleId๋ ์ด์ ๊ณผ ๋ค๋ฅด๊ฒ id๋ผ๋ params๋ฅผ ๊ฐ์ง๋ค. ๋ํ PATCH, DELETE๋ฅผ ๊ฐ์ง๋ค.
๊ทผ๋ฐ ๋ฑ ์ด๊ฒ๋ค ๋ง๊ณ ๋ article๊ณผ ๊ฑฐ์ ๋์ผํ ๋ฐฉ์์ผ๋ก ์์ฑํ๋ฉด ๋๋ค.
type Params = {
articleId: string;
};
// articleId ์์ ํ๊ฒ number๋ก ์ ํ
function parseArticleId(articleId: string) {
const id = parseInt(articleId, 10);
if (isNaN(id) || id <= 0) {
throw new Error('Invalid articleId');
}
return id;
}
ํจ์๋ฅผ ์์ฑํ๊ธฐ์ ์์ ํ๋ผ๋ฏธํฐ ํ์ ๊ณผ ๊ณตํต ํจ์๋ฅผ ์์ฑํด์ฃผ์๋ค.
params์ ๋ฌธ์์ด๋ก ๋ค์ด์ค๋๋ฐ ์๋ฒ์์๋ id๋ฅผ number๋ก ์ ์ํ์ฌ, ์์ ํ๊ฒ number๋ก ๋ฐ๊ฟ์ฃผ๋ ํจ์์ด๋ค.
GET articleId
export async function GET(_request: NextRequest, { params }: { params: Promise<Params> }) {
try {
const { articleId } = await params; //** await์ ๋ถ์ฌ์ params๋ฅผ ๋จผ์ ๊ฐ์ ธ์์ผ ํ๋ค
const id = parseArticleId(articleId);
const data = await safeFetch(`${API.ARTICLES}${articleId}`);
return NextResponse.json({
message: `ArticleID: ${articleId}`,
data: data,
});
} catch (err) {
return handlerServerError(err, 'Failed to get article');
}
}
request๊ฐ ํ์ฌ๋ ์ฐ์ด์ง ์๊ณ ์์ด์ _๋ฅผ ๋ถ์ฌ์คฌ๋ค.
์ฌ๊ธฐ์ ์ค์ํ ๊ฒ Next.js 15๋ถํฐ๋ params๊ฐ ๋น๋๊ธฐ๋ก ๋ณ๊ฒฝ๋์ด(type์ด Promise), params์ ์ ๊ทผํ ๋ await์ผ๋ก ๋จผ์ ์ ๊ทผํด์ผํ๋ ๊ฒ์ด๋ค.
1. ํ๋ผ๋ฏธํฐ๋ก๋ถํฐ articleId๋ฅผ ๊ฐ์ ธ์จ๋ค.
2. id๋ฅผ API.ARTICLES ๋ค์ ๋ถ์ฌ์ safeFetch๋ฅผ ํ๋ค.
3. ๋ฐ์์จ ๋ฐ์ดํฐ๋ฅผ ๋๊ธด๋ค.
articles์์๋ ์ฌ๋ฌ ๊ฒ์๊ธ์ ์กฐํํ๊ธฐ ๋๋ฌธ์ ํ์ด์ง, ์ ๋ ฌ, ๊ฒ์์ด๊ฐ์ ์ฟผ๋ฆฌํ๋ผ๋ฏธํฐ๋ฅผ url์์ ์ฝ๊ธฐ ์ํด request.url์ด ์ฌ์ฉ๋์๋ค.
ํ์ง๋ง articleId๋ ๋์ ๊ฒฝ๋ก( [articleId] )๋ก ํ์ํ Id๋ฅผ ์ด๋ฏธ ๋ฐ๊ณ ์์ด url์ ์ฝ์ ํ์๊ฐ ์์ด request๊ฐ ์ฐ์ด์ง ์๋๋ค.
PATCH articleId
export async function PATCH(request: NextRequest, { params }: { params: Params }) {
try {
const articleId = parseArticleId(params.articleId);
const body = await request.json();
const { image, title, content } = body;
const payload = { image, title, content };
const data = await safeFetch(`${API.ARTICLES}${articleId}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
return NextResponse.json({
message: `ArticleID: ${articleId} updated`,
data: data,
});
} catch (err: unknown) {
return handlerServerError(err, 'Failed to patch article');
}
PATCH๋ ๊ธฐ์กด ๋ด์ฉ์ ์์ ํ๋ ๊ฑฐ์ฌ์ request.json()์ผ๋ก ๊ธฐ์กด ๋ด์ฉ์ธ body๋ฅผ ์ฝ์ด์จ๋ค.
method๋ก 'PATCH'๋ฅผ ๋ ๋ ค์ ์๋ฒํํ ์ด ์์ฒญ์ด ์์ ์์ฒญ์์ ์๋ฆฐ๋ค.
DELETE articleId
export async function DELETE(_request: NextRequest, { params }: { params: Params }) {
try {
const articleId = parseArticleId(params.articleId);
const data = await safeFetch(`${API.ARTICLES}${articleId}`, { method: 'DELETE' });
return NextResponse.json({
message: `ArticleID: ${articleId} deleted`,
data: data,
});
} catch (err) {
return handlerServerError(err, 'Failed to delete article');
}
}
delete๋ method๋ก 'DELETE'๋ฅผ ๋ ๋ ค์ ์ญ์ ์์ฒญ์ ๋ณด๋ธ๋ค.
์ฐธ๊ณ
'Trouble Shooting & Issues > Linkiving' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| safeFetch ์ ํธํจ์ ๋ง๋ค๊ธฐ(2): safeFetch ํจ์ ์์ฑ (0) | 2025.12.12 |
|---|---|
| safeFetch ์ ํธํจ์ ๋ง๋ค๊ธฐ(1) : Error ํด๋์ค ์ ์ (0) | 2025.12.12 |
| [Next.js] ์ฌ์ฉ์๊ฐ ์ ๋ ฅํ ๋งํฌ์ ์ธ๋ค์ผ ๊ฐ์ ธ์ค๊ธฐ (0) | 2025.11.17 |
| ์ธํฐ๋ ์ ์ด ๊ฐ๋ฅํ ํ๊ทธ ์ค์ฒฉ ์์ํค๊ธฐ (0) | 2025.11.05 |
| [WIP] CardList Pagination ๊ตฌํํ๊ธฐ (0) | 2025.11.04 |
GitHub