2025. 11. 17. 00:33ใTrouble Shooting & Issues/Linkiving

๋ชฉ์ฐจ
1. ์ํฉ
2. og ํ์ฉํ๊ธฐ
3. ๋ ์ข๊ฒ ๋ง๋ค๊ธฐ
1. ์ํฉ
๊ธฐ์กด ์ ์ํด๋ BookmarkCard ์ปดํฌ๋ํธ

์นด๋ ์ปดํฌ๋ํธ๋ ์ฌ์ฉ์๊ฐ ๋ถ๋งํฌํ ๋งํฌ์ url๋ก๋ถํฐ ์ธ๋ค์ผ์ ๊ฐ์ ธ์ ๊ฐ์ด ๋ณด์ฌ์ฃผ๋ ์ญํ ์ ๊ฐ์ง๋ค.
๊ทธ๋ฐ๋ฐ ๊ธฐ์กด ์ฝ๋์์๋ ์ผ๋จ ์์๋ก ์ด๋ฏธ์ง URL์ ์ง๋นต์ผ๋ก ๋ฃ์ด์ ์ด๊ฑธ url๋ก๋ถํฐ ๊ฐ์ ธ์ค๋๋ก ๋ฐ๊ฟ์ฃผ์ด์ผ ํ๋ ์ํฉ์ด๋ค.
์ง๊ธ์ ๊ทธ๋ฅ ๋จ์ํ๊ฒ props๋ก imageUrl์ ๋ฐ์์ public์ ์๋ ๊ธฐ๋ณธ ์ด๋ฏธ์ง globe.svg๋ฅผ ๋ฐ๋ก ๋ฃ๊ณ ์๋ค.
2. cheerio์ og ์ฌ์ฉํ๊ธฐ
1. API Route์ GET ํธ๋ค๋ฌ ํจ์ ์ ์
// src/app/api.og-thumbnail/route.ts
import * as cheerio from 'cheerio';
import { NextResponse } from 'next/server';
export async function GET(req: Request) {
const { searchParams } = new URL(req.url);
const targetUrl = searchParams.get('url');
if (!targetUrl) {
return NextResponse.json({ error: 'url is required' }, { status: 400 });
}
try {
const html = await fetch(targetUrl).then(res => res.text());
const $ = cheerio.load(html);
const ogImage =
$('meta[property="og:image"]').attr('content') ||
$('meta[name="twitter:image"]').attr('content');
return NextResponse.json({ image: ogImage || null });
} catch (e) {
return NextResponse.json({ image: null });
}
}
์ธ๋ถ ์ด๋ฏธ์ง๋ฅผ ๊ฐ์ ธ์ค๊ธฐ ์ํด์ ์์ฑํ ํ์ผ์ด๋ค. ํ๋ํ๋ ์ดํด๋ณด์.
import * as cheerio from 'cheerio';
import { NextResponse } from 'next/server';
- cheerio: Next ์๋ฒ(Node.js)์์ HTML์ ํ์ฑํ๊ธฐ ์ํด ์ฌ์ฉํ๋ npm ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก,
HTML DOM์ jQuery์ฒ๋ผ ํ์ํ๊ณ ์กฐ์ํ ์ ์๋ค. (OG ํ๊ทธ ์ถ์ถ์ฉ) - NextResponse: Next.js App Router์์ API Router์ ์๋ต์ ์ฝ๊ฒ ๋ฐํํ๊ธฐ ์ํด ์ฌ์ฉํ๋ค.
export async function GET(req: Request) {
const { searchParams } = new URL(req.url);
const targetUrl = searchParams.get('url');
- Next.js์ App Router์์๋ app/api/ํด๋๋ช /route.ts ๋ด๋ถ์ ์์ ๊ฐ์ ํ์์ผ๋ก ์์ฑํ๋ฉด api/ํด๋๋ก ๋ค์ด์ค๋ GET ์์ฒญ์ ์๋์ผ๋ก ์ด ํจ์๊ฐ ์ฒ๋ฆฌํ๊ฒ ๋๋ค.
- ํด๋ผ์ด์ธํธ ์์ฒญ์ ์ฒ๋ฆฌํ๋ ์๋ฒ์ธก ์๋ํฌ์ธํธ๋ฅผ ์ ์ํ๋ค. api/og-thumbnail๊ฐ์ URL์ GET/POST ์์ฒญ์ ๋ณด๋ด๋ฉด ์๋ฒ์์ ์ด ํจ์๊ฐ ์คํ๋์ด ๋ฐ์ดํฐ๋ฅผ ๋ฐํํ๋ค.
- Node.js์ ๋ด์ฅ ๊ฐ์ฒด URL(์์ฑ์)์ ์ฌ์ฉํด์ ์์ฒญ์ผ๋ก ๋ฐ์ req์์ url์ ๋ฐ์์ค๊ณ , ํด๋น url์์ ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ(url)์ ๊ฐ์ ๋ฐ์์จ๋ค.
ex) /api/og-thumbnail?url='https://naver.com → targetUrl = 'https://naver.com'
๊ณผ ๊ฐ์ด req๋ก ๋ฐ์ url์์ url ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ์ ํด๋นํ๋ ๊ฐ์ ๋ฝ์๋ธ๋ค.
if (!targetUrl) {
return NextResponse.json({ error: 'url is required' }, { status: 400 });
}
๋ง์ฝ ์๋ฒ์์ fetchํ URL์ด ์์ผ๋ฉด 400 Bad Request๋ฅผ ๋ฐํํ์ฌ ์ฒ๋ฆฌ๋ฅผ ์ค๋จํ๋ค.
try {
const html = await fetch(targetUrl).then(res => res.text());
const $ = cheerio.load(html);
const ogImage =
$('meta[property="og:image"]').attr('content') ||
$('meta[name="twitter:image"]').attr('content');
return NextResponse.json({ image: ogImage || null });
} catch (e) {
return NextResponse.json({ image: null });
}
ํด๋ผ์ด์ธํธ์์๋ CORS ๋ฌธ์ ๋ก ์ธ๋ถ HTML์ fetchํ ์ ์๊ธฐ ๋๋ฌธ์ next ์๋ฒ์์ ํ๋ก์ ์ญํ ์ ์ํํ๋ค.
HTML๋ฌธ์(res) ์ ์ฒด๋ฅผ ๋ฌธ์์ด๋ก ๊ฐ์ ธ์, cheerio๋ก ํ์ฑํ๋ค.
OG ํ๊ทธ, twitter ํ๊ทธ๋ฅผ ๋จผ์ ํ์ํ๊ณ , ๋ฐ๊ฒฌ๋์ง ์์ผ๋ฉด null์ ๋ฐํํ๋ค.
์์ธ ์ํฉ ๋ฐ์ํ์ ๋ ์ญ์ null์ ์์ ํ๊ฒ ๋ฐํํ๋ค.
** ํ๋ก์ : ํด๋ผ์ด์ธํธ์ ์ธ๋ถ ์๋ฒ ์ฌ์ด์์ ์ค๊ณ ์ญํ ์ ํ๋ ์๋ฒ
2. useThumbnail ์ปค์คํ ํ ์ ์
string ํ์ ์ link๋ฅผ props๋ก ๋ฐ์์ ์ธ๋ค์ผ์ ๋ก๋ํ๋ ํ , useThumbnail์ ์ ์ํ๋ค.
'use client';
import { useEffect, useState } from 'react';
const thumbnailCache = new Map<string, string | null>();
const defaultImage = '/file.svg';
interface ThumbnailState {
thumbnail: string | null;
loading: boolean;
error: boolean;
}
export function useThumbnail(link: string) {
const [state, setState] = useState<ThumbnailState>({
thumbnail: null,
loading: true,
error: false,
});
- thumbnailCache: ์ต์ ํ๋ฅผ ์ํด ์ธ๋ค์ผ ์บ์๋ฅผ ์ ์ฅํ thumbnailCache๋ฅผ ์ ์ํ๋ค.
- defaultImage: ์ธ๋ค์ผ์ ๋ถ๋ฌ์ค์ง ๋ชปํ์ ๋ ๋์ธ ๊ธฐ๋ณธ ์ด๋ฏธ์ง์ด๋ค. ์์ง ์ ์์ด ์๋์ด์ ์์๋ก next ๊ธฐ๋ณธ ์ด๋ฏธ์ง๋ฅผ ๋ฃ์๋ค.
- useState: ํด๋ผ์ด์ธํธ๊ฐ API ํธ์ถ์ ํตํด ๋น๋๊ธฐ์ ์ผ๋ก ์ด๋ฏธ์ง๋ฅผ ๊ฐ์ ธ์ค๊ธฐ ๋๋ฌธ์ useState๋ก ์ธ๋ค์ผ ์ํ๋ฅผ ๊ด๋ฆฌํด์ฃผ๊ณ , loading๊ณผ error ์ํ๋ ๊ฐ์ด ๊ด๋ฆฌํด์ค๋ค.
์ฝ๋๋ฅผ ์์ฑํ๋ค๋ณด๋ thumbnail, loading, error๋ฅผ ํ๊บผ๋ฒ์ ๊ด๋ฆฌํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ state๋ก ๋ฌถ์ด์ ์ ์ํ๋ค.
useEffect(() => {
if (!/^https?:\/\//i.test(link)) {
setState({ thumbnail: defaultImage, loading: false, error: false });
return;
}
// check cache
if (thumbnailCache.has(link)) {
setState({
thumbnail: thumbnailCache.get(link) || defaultImage,
loading: false,
error: false,
});
return;
}
let isMounted = true;
- if๋ฌธ: ๋งํฌ๊ฐ ์ ํจํ์ง ์์ ๊ฒฝ์ฐ, defaultImage๋ฅผ ๋ฆฌํดํ๋ค.
- check cache: ๋งํฌ๊ฐ ์ ํจํ ๊ฒฝ์ฐ, ๊ฐ์ฅ ๋จผ์ ์บ์ ์กด์ฌ ์ฌ๋ถ๋ฅผ ์ฒดํฌํ์ฌ ์์ ๊ฒฝ์ฐ, ํด๋น ์บ์ ๋ฐ์ดํฐ๋ฅผ ๋ฆฌํดํ๋ค.
- isMounted: ์ปดํฌ๋ํธ๊ฐ ์ธ๋ง์ดํธ๋ ํ์๋ setState ํธ์ถ์ ๋ฐฉ์งํ๊ธฐ ์ํ ํ๋๊ทธ๋ก,
๋น๋๊ธฐ fetch๊ฐ ๋ฆ๊ฒ ๋๋๋๋ผ๋ ์ด๋ฏธ ์ธ๋ง์ดํธ๋ ์ปดํฌ๋ํธ์ ์ํ๋ฅผ ์ ๋ฐ์ดํธํ์ง ์๋๋ก ํ๋ค.
async function load() {
setState(prev => ({ ...prev, loading: true, error: false }));
try {
const res = await fetch(`/api/og-thumbnail?url=${encodeURIComponent(link)}`);
if (!res.ok) throw new Error('Failed to fetch thumbnail');
const data = await res.json();
const imageUrl = data.image || defaultImage;
// save to cache
thumbnailCache.set(link, imageUrl);
if (isMounted) {
setState({ thumbnail: imageUrl, loading: false, error: false });
}
} catch {
if (isMounted) {
setState({ thumbnail: defaultImage, loading: false, error: true });
}
}
}
๋ค์์ผ๋ก๋ ์ด์ ๋จ๊ณ์์ og-thumbnail์์ ์ ์ํ GETํจ์๋ฅผ ์ฌ์ฉํ์ฌ ์ธ๋ค์ผ์ ๋ก๋ํ๋ค.
- encodeURIComponent: URL ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ์ ์์ ํ๊ฒ ๋ฃ๊ธฐ ์ํด ํน์๋ฌธ์๋ฅผ ์ธ์ฝ๋ฉํ๋ ํจ์์ด๋ค.
- res: ์๋ฒ API(/api/og-thumbnail) ํธ์ถ
- data: ์๋ต ์ํ ํ์ธ ๋ฐ JSON ํ์ฑ
- imageURL: OG ์ด๋ฏธ์ง URL ์ถ์ถ
- ์ฑ๊ณต ์, ์บ์ ์ ์ฅ ๋ฐ ์คํจ์ fallback ์ด๋ฏธ์ง ์ค์
load();
return () => {
isMounted = false;
};
}, [link]);
return state;
}
load() ํจ์๋ฅผ ํธ์ถํ๊ณ , isMounted๋ฅผ ๋ฆฌํดํ๋ค.
ํ ์์ ์ต์ข ๋ฆฌํดํ๋ ๊ฒ์ ๊ฐ์ฅ ์ฒ์ ์ ์ํ๋ state(thumbnail, loading, error)๊ฐ ๋๋ค.
3. next.config.ts ์ธํ ๋ฐ ํ ํ์ฉ
next.js๋ ์ด๋ฏธ์ง ์ต์ ํ๋ฅผ ์ํด์ ์ธ๋ถ์์ ์ด๋ฏธ์ง๋ฅผ ๊ฐ์ ธ์ฌ ๊ฒฝ์ฐ, ๊ฐ๋ฐ์๊ฐ ๋ฏธ๋ฆฌ ๋งํด๋ ๋งํฌ์ ์ด๋ฏธ์ง๋ง ๊ฐ์ ธ์ฌ ์ ์๋๋ก ํ๋ค.
์ฐ๋ฆฌ ์๋น์ค์์ ์ด๋ค ๋งํฌ๋ฅผ ์ฌ์ฉ์๊ฐ ๋ถ๋งํฌํ ์ง ๋ชจ๋ฅด๋ฏ๋ก, ํ๋ํ๋ ์ถ๊ฐํ๊ธฐ์๋ ๋๋ฌด ๋ง์ ๊ฒ ๊ฐ์์ ์ฐ์ http, https๋ก ์์ํ๋ ๋งํฌ๋ฅผ ๊ฐ๋ฅํ๋๋ก ์ธํ ํด์ค๋ค.
// next.config.ts
images: {
remotePatterns: [
{
protocol: 'http',
hostname: '**',
},
{
protocol: 'https',
hostname: '**',
},
],
},
ํ ํ์ฉ
const thumbnail = useThumbnail(link); // url์์ ์ธ๋ค์ผ ๋ฝ๋ ํ
์ด๋ ๊ฒ thumbnail์ ํ ์ ์ฌ์ฉํด์ ์ ์ํ๊ณ , ์ด๋ฏธ์ง ๋งํฌ๋ฅผ ๋ฃ์ด์ผ ํ๋ ๊ณณ์ ๋ฃ์ด์ฃผ๋ฉด ๋๋ค.
3. ๋ ์ข๊ฒ ๋ง๋ค๊ธฐ
1. ์บ์ ์์ ๋ณ๊ฒฝ useThumbnail → route.ts
์๊ฐํด๋ณด๋๊น ์ด๋ฏธ์ง๋ฅผ ๋ฐ์์ค๋ ๋ถ๋ถ์ route.ts์ธ๋ฐ, ์บ์ ์ ์ฅ์ ํ ๊ฑฐ๋ฉด ํ ์ด ์๋๋ผ ๋ผ์ฐํธ ๋ถ๋ถ์์ ํ๋ ๊ฒ ๋ง๋ค๊ณ ๋๊ผ๋ค.
๊ทธ๋์ useThumbnail์์ ์บ์ํ๋ ๋ถ๋ถ์ ์ง์ฐ๊ณ route.ts์ ์บ์๋ฅผ ์ถ๊ฐํ๋ค.
const cache = new Map<string, { image: string | null; expires: number }>();
const CACHE_TTL = 1000 * 60 * 60 * 24 * 14; // 14์ผ
์ฝ๋ ๊ฐ์ฅ ์๋ถ๋ถ์ ์ด๋ ๊ฒ ์บ์๋ฅผ ์ ์ํด์คฌ๋ค.
๋ถ๋งํฌํ ๋ธ๋ก๊ทธ ๊ธ์ ์ด๋ฏธ์ง๊ฐ ๊ฑฐ์ ๋ฐ๋์ง ์์ ๊ฒ์ด๋ฏ๋ก 14์ผ๋ก ์ค์ ํ๋๋ฐ ๋ ๊ธธ๊ฒ ํด๋ ๊ด์ฐฎ์ ๊ฒ ๊ฐ๊ธดํ๋ค.
์ด๊ฑด ๋ ์์๋ณด๊ณ ์์ฑํด๋ด์ผ๊ฒ ๋ค.
const cached = cache.get(targetUrl);
const now = Date.now();
if (cached && cached.expires > now) {
return NextResponse.json(
{ image: cached.image },
{
headers: {
'Cache-Control': 'public, max-age=1209600', // 14์ผ
},
}
);
}
url์ด ์ ํจํ์ง ํ์ธํ ์งํ์ ์บ์๋ฅผ ํ์ธํ๋ ์ฝ๋๋ฅผ ์์ฑํ๋ค.
// ์บ์ ์ ์ฅ
cache.set(targetUrl, { image: ogImage, expires: now + CACHE_TTL });
return NextResponse.json(
{ image: ogImage },
{
headers: {
'Cache-Control': 'public, max-age=1209600', // ๋ธ๋ผ์ฐ์ /ํ๋ก์ ์บ์
},
}
);
try๋ฌธ ๋ด์์๋ ์ญ์ image๋ฅผ ์๋ก ๊ฐ์ ธ์์ ๊ฒฝ์ฐ, ์บ์์ ์ ์ฅํ๋ ์ฝ๋๋ฅผ ์์ฑํ๋ค.
2. ๋ฉํํ๊ทธ๊ฐ ์ค์ ์ด ์ ์๋์ด์๋ ๊ฒฝ์ฐ
ํ์ฌ ์์ฑํ GET์ ๋ณด๋ฉด og์ twitter์์ ์ด๋ฏธ์ง๋ฅผ ์ป์ด์ค๋๋กํ๊ณ ์๋๋ฐ,๋ฉํํ๊ทธ์ ์ด๋ฏธ์ง๊ฐ ์ธํ ์ด ์๋์ด์๋ ๊ฒฝ์ฐ๋ ์์ ์ ์์ ๊ฒ์ด๋ค.๊ทธ๋ด ๋ ๊ฐ๋จํ๊ฒ ํด๋น ํ์ด์ง์ ์ฒซ ๋ฒ์งธ <img> ํ๊ทธ๋ฅผ ๊ฐ์ ธ์ค๋๋ก ํ ์๋ ์์ ๊ฒ์ด๋ค.
ํ์ง๋ง ์ด๋ ๊ฒ ํ ๊ฒฝ์ฐ ๋จ์ ์ด ์๋ค.
- ์ด๋ฏธ์ง ํฌ๊ธฐ ์์ธก ๋ถ๊ฐ
OG ์ด๋ฏธ์ง๋ ์ต์ ํ๋ ํฌ๊ธฐ์ด๋, img๋ ์ต์ ํ๊ฐ ๋ ์ด๋ฏธ์ง์์ ํ์ ํ ์ ์์ด ๋คํธ์ํฌ ๋ถ๋ด์ด ๊ฐ ์ ์๋ค. - ์๋ชป๋ ์ด๋ฏธ์ง ์ ํ ๊ฐ๋ฅ
์ฒซ ๋ฒ์งธ img๊ฐ ๋ก๊ณ , ๊ด๊ณ ๋ฐฐ๋ ๋ฑ์ด ๋ ์๋ ์๋๋ฐ, ์ด๋ UX ์ธก๋ฉด์์ ๋ถ์ ์ ํ ์ ์๋ค. (์ด์ํ ๊ด๊ณ ์ด๋ฏธ์ง ๊ฐ์ ธ์ค๋ฉด ์ด๋กํด!) - ์๋ฒ ์ธก ์ฒ๋ฆฌ ์๊ฐ ์ฆ๊ฐ
OG ์ด๋ฏธ์ง๋ meta ํ๊ทธ๋ง ์ฝ์ผ๋ฉด ๋์ง๋ง, ์ฒซ ๋ฒ์งธ img๋ฅผ ๋ถ๋ฌ์ค๋ ค๋ฉด HTML ์ ์ฒด๋ฅผ ํ์ฑํด์ผํ๋ฉฐ, ํ์์ URL ๋ณด์ ๊น์ง ํด์ผํ๋ค.
๋จ์ ์ด ์น๋ช ์ ์ธ ๊ฒ ๊ฐ์์ ๋ฉํํ๊ทธ์ ์ด๋ฏธ์ง๊ฐ ์๋ค๋ฉด ๊ธฐ๋ณธ ์ด๋ฏธ์ง๋ฅผ ๋์ฐ๊ธฐ๋ก ํ๋ค.
์๋ง AI๊ฐ ๋ ํฌ๊ฒ ๋ฐ์ ํ๋ฉด AI ์์ฑํ ์ด๋ฏธ์ง๋ฅผ ๋์๋ ์ข์ ๊ฒ ๊ฐ๋ค.
3. image ํ์ฉ url์ ๋ณด์๊ณผ ์ฑ๋ฅ
๋ค์ํ ๋ถ๋งํฌ๋ฅผ ์ง์ํ๊ธฐ ์ํด์ next.config.ts์์ hostname: '**'๋ก ์ธํฐ๋ท ์์ ๋ชจ๋ images๋ฅผ ๊ฐ์ ธ์ฌ ์ ์๋๋ก ํ์ฉํ์๋ค.
ํ์ง๋ง ๋ณด์๊ณผ ์ฑ๋ฅ ์ธก๋ฉด์์ ๊ฑฑ์ ๋๋ ๋ถ๋ถ์ด ์์๋ค.
- ๋ณด์
์ธ๋ถ URL์ ๊ทธ๋๋ก ๋ก๋ → ์ ์ฑ ์ด๋ฏธ์ง, XSS, SSRF ๊ณต๊ฒฉ ๊ฐ๋ฅ
๊ณต๊ฒฉ์๊ฐ ์์๋ก ์๋ฒ ์์ฒญ์ ์ ๋ํ ์ ์์ - ์ฑ๋ฅ
Next.js Image ์ต์ ํ ๊ณผ์ ์์ ๋ชจ๋ ์ด๋ฏธ์ง์ ๋ํด resizing, WebP ๋ณํ ๋ฑ์ ์๋ฒ ์ฒ๋ฆฌ๋ฅผ ์๋ํ์ฌ, ๋๋ฉ์ธ์ด ๋ง์ผ๋ฉด CDN ์บ์ฑ์ด ์ด๋ ค์์ง๊ณ ์ฒซ ๋ก๋ ์๋๊ฐ ๋๋ ค์ง ์ ์์
ํ๋ก ํธ์์ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด์๋ ๋จ์ํ๊ฒ ์๊ฐํ๋ฉด ์ธ ๊ฐ์ง์ ํด๊ฒฐ์ฑ ์ด ์๋ค.
- ์ปค์คํ ๋ก๋(Custom loader)๋ฅผ ์ฌ์ฉํ์ฌ <img>์ฒ๋ผ ์ถ๋ ฅ (Next.js์ ์ต์ ํ ํฌ๊ธฐ)
- ์ ๋ขฐ๋ ๋๋ฉ์ธ๋ง ํ์ดํธ๋ฆฌ์คํธ ์ฒ๋ฆฌ (๋ชจ๋ ๋งํฌ๋ฅผ ๋ฆฌ์คํธ์ ์ถ๊ฐํ๋ ๊ฒ์ด ํ์ค์ ์ผ๋ก ๋ถ๊ฐ๋ฅํจ + 50๊ฐ ์ ํ ์์)
- ํ๋ก์ ์๋ฒ๋ฅผ ์ฌ์ฉํ์ฌ ์ด๋ฏธ์ง ์บ์ฑ ํ ํด๋ผ์ด์ธํธ์ ์ ๊ณต (์๋ฒ ๋ถ๋ด์ด ์ปค์ง)
์ด ๋ถ๋ถ ํด๊ฒฐํ๋ ค๊ณ ์ด๊ฒ์ ๊ฒ ํด๋ดค๋๋ฐ ๋๊ธฐ๋ค์ด๋ ์๊ธฐํ๋๊น ๊ฒฐ๊ตญ ์๋ฒ์์ ์ฒ๋ฆฌํ๋ ๊ฒ ๋ง๋ ๊ฒ ๊ฐ๋ค๋ ๊ฒฐ๋ก ์ด ๋์๋ค..^_^..
์๋ ๋ค๋ค ๋ชฐ๋์ด์ ํ์ฌ ์๋ฒ์์ ์ด๋ฏธ์ง ์ ์ฅ์ ์๊ฐ์ ์ํ๊ณ ์์๋๋ฐ ๊ทธ๋๋ ๋คํํ ์ด๋ฅธ ์์ ์ ์ด๋ฏธ์ง ์ ์ฅ์ ๊ตฌํํด์ผํ๋ค๋ ์๊ธฐ๊ฐ ๋์์ ๋คํ์ด๋ค.
ํ๋ก ํธ์์ ์ด๋ฏธ์ง๋ฅผ ์๋ฒ๋ก ๋ณด๋ด์ฃผ๋ฉด ์๋ฒ์์ ํฌ๋กค๋ง์ ํตํด ์ต์ ํ๋ ์ด๋ฏธ์ง๋ฅผ ์ ์ฅํ๊ณ , ํ๋ก ํธ๋ก ๋ณด๋ด์ฃผ๋ ๋ฐฉ์์ด๋ค.
์ฐธ๊ณ
์ฐธ๊ณ ์๋ฃ๋งํฌ
'Trouble Shooting & Issues > Linkiving' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| safeFetch ์ ํธํจ์ ๋ง๋ค๊ธฐ(1) : Error ํด๋์ค ์ ์ (0) | 2025.12.12 |
|---|---|
| Next.js app router๋ก api ํจ์ ์์ฑํ๊ธฐ (0) | 2025.11.27 |
| ์ธํฐ๋ ์ ์ด ๊ฐ๋ฅํ ํ๊ทธ ์ค์ฒฉ ์์ํค๊ธฐ (0) | 2025.11.05 |
| [WIP] CardList Pagination ๊ตฌํํ๊ธฐ (0) | 2025.11.04 |
| ๊ธฐ๋ณธ TextArea์ ํ์ฅ์ฑ ๊ณ ๋ฏผ (์ ์ ? ๋์ ?) (0) | 2025.11.03 |
GitHub