Next.js 16 Turbopack์์ SVG๋ฅผ React ์ปดํฌ๋ํธ๋ก ์ฌ์ฉํ๊ธฐ
Next.js 16์์๋ experimental.turbo ์ต์
์ด ์ ๊ฑฐ๋์ด ๊ธฐ๋ณธ๊ฐ์ด ๋์ต๋๋ค.
์ด๋ฐ ํ๊ฒฝ์์ SVG๋ฅผ ์ฌ์ฉํ ๋ ์ด๋ป๊ฒ ํด์ผํ๋์ง ์์๋ณด๊ฒ ์ต๋๋ค.
Next.js ํ๋ก์ ํธ์์ SVG ์์ด์ฝ์ ๊ด๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ์ฌ๋ฌ ๊ฐ์ง๊ฐ ์์ต๋๋ค. ๊ฐ์ฅ ์ผ๋ฐ์ ์ธ ๋ฐฉ๋ฒ์ img ํ๊ทธ๋ก ์ฌ์ฉํ๊ฑฐ๋ react-icons, lucide ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด์ง๋ง, ์ด ๋ฐฉ๋ฒ๋ค์ ๊ฐ๊ฐ์ ๋จ์ ์ด ์์ต๋๋ค.
imgํ๊ทธ: CSS๋ก ์์ ๋ณ๊ฒฝ์ด ์ด๋ ต๊ณ , ๋งค๋ฒ ๋คํธ์ํฌ ์์ฒญ์ด ํ์ํ ์ ์์ต๋๋ค.- ์์ด์ฝ ๋ผ์ด๋ธ๋ฌ๋ฆฌ: ์ปค์คํ ๋์์ธ ์์คํ ์ ์์ด์ฝ์ ์ฌ์ฉํ๊ธฐ ์ด๋ ต์ต๋๋ค.
์ด๋ฒ ๊ธ์์๋ Next.js ์ Turbopack๊ณผ @svgr/webpack์ ์ฌ์ฉํ์ฌ SVG ํ์ผ์ React ์ปดํฌ๋ํธ๋ก ๋ณํํ๊ณ , TypeScript์ ํ์ ์์คํ ์ ํ์ฉํ ํ์ ์์ ํ ์์ด์ฝ ๊ด๋ฆฌ ์์คํ ์ ๊ตฌ์ถํ๋ ๋ฐฉ๋ฒ์ ์๊ฐํฉ๋๋ค.
๋ฌธ์ ์ํฉ
Next.js 16๋ถํฐ ๊ธฐ๋ณธ ๋ฒ๋ค๋ฌ๊ฐ Turbopack์ผ๋ก ๋ณ๊ฒฝ๋์์ต๋๋ค. Turbopack์์ SVG๋ฅผ React ์ปดํฌ๋ํธ๋ก ์ฌ์ฉํ๋ ค๊ณ ํ๋ฉด ๋ค์๊ณผ ๊ฐ์ ์๋ฌ๊ฐ ๋ฐ์ํฉ๋๋ค.
Failed to parse svg source code for image dimensions
Caused by:
- Source code does not contain a <svg> root element
์ด๋ Turbopack์ด SVG ํ์ผ์ ์ ๋๋ก ์ฒ๋ฆฌํ์ง ๋ชปํด์ ๋ฐ์ํ๋ ๋ฌธ์ ์ ๋๋ค.
Step 1: next.config.ts ์ค์
@svgr/webpack ์ค์น
๋จผ์ ํ์ํ ํจํค์ง๋ฅผ ์ค์นํฉ๋๋ค:
npm install -D @svgr/webpack
Turbopack ์ค์
Next.js ๊ณต์ ๋ฌธ์์ ๋ฐ๋ฅด๋ฉด, Turbopack์์ Webpack ๋ก๋๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด as: '*.js' ์ต์
์ ๋ฐ๋์ ์ถ๊ฐํด์ผ ํฉ๋๋ค.
// next.config.ts
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
reactCompiler: true,
turbopack: {
rules: {
'*.svg': {
loaders: ['@svgr/webpack'],
as: '*.js', // ์ด ์ต์
์ด ์์ผ๋ฉด ์๋ฌ ๋ฐ์!
},
},
},
};
export default nextConfig;
์ค์ ์์ธ ์ค๋ช
turbopack.rules: Turbopack์ ํ์ผ ๋ณํ ๊ท์น์ ์ ์ํ๋ ๊ฐ์ฒด์ ๋๋ค. Webpack์module.rules์ ์ ์ฌํ ์ญํ ์ ํฉ๋๋ค.'*.svg': ๋ชจ๋.svgํ์ฅ์๋ฅผ ๊ฐ์ง ํ์ผ์ ๋ํด ์ด ๊ท์น์ ์ ์ฉํฉ๋๋ค.loaders: ['@svgr/webpack']: SVG ํ์ผ์ ์ฒ๋ฆฌํ ๋ก๋๋ฅผ ์ง์ ํฉ๋๋ค.@svgr/webpack์ SVG๋ฅผ React ์ปดํฌ๋ํธ๋ก ๋ณํํด์ฃผ๋ ๋ก๋์ ๋๋ค.as: '*.js'(์ค์!): ์ด ์ต์ ์ด ๊ฐ์ฅ ์ค์ํฉ๋๋ค. ๋ณํ๋ SVG๋ฅผ JavaScript ๋ชจ๋๋ก ์ฒ๋ฆฌํ๋๋ก Turbopack์ ์ง์ํฉ๋๋ค.
// as: '*.js'๊ฐ ์์ผ๋ฉด
// Turbopack์ด ๋ณํ๋ ๊ฒฐ๊ณผ๋ฅผ ์ด๋ป๊ฒ ์ฒ๋ฆฌํด์ผ ํ ์ง ๋ชฐ๋ผ์ ์๋ฌ ๋ฐ์
// as: '*.js'๊ฐ ์์ผ๋ฉด
// SVG โ React Component โ JavaScript Module๋ก ์ฌ๋ฐ๋ฅด๊ฒ ๋ณํ
๊ฒฐ๊ณผ์ ์ผ๋ก ์ฌ๊ธฐ๊น์ง ์ค์ ํ๋ฉด Next.js 16 Tuebopack์์ SVGR์ ์ด์ฉํด SVG ์์ด์ฝ์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
Step 2: ์์ด์ฝ ์์คํ ๊ตฌ์ถ
๋จ์ํ SVG๋ฅผ importํด์ ์ฌ์ฉํ ์๋ ์์ง๋ง, ๋ ๋์ ๊ฐ๋ฐ์ ๊ฒฝํ์ ์ํด ์์ด์ฝ ์์คํ ์ ๊ตฌ์ถํ ์ ์์ต๋๋ค.
๋๋ ํ ๋ฆฌ ๊ตฌ์กฐ
์๋์ ๊ฐ์ ํ์ผ๊ตฌ์กฐ๋ก svg๊ฐ ์๋ค๊ณ ๊ฐ์ ํฉ๋๋ค.
src/
shared/
assets/
icons/
โโโ arrow_left.svg
โโโ chart.svg
โโโ comment.svg
โโโ dashboard.svg
โโโ index.ts // ํต์ฌ ํ์ผ
icons/index.ts ๊ตฌํ
// src/shared/assets/icons/index.ts
// 1. ๋ชจ๋ ์์ด์ฝ์ import
import ArrowLeft from './arrow_left.svg';
import Chart from './chart.svg';
import Comment from './comment.svg';
import Dashboard from './dashboard.svg';
// 2. ์์ด์ฝ ๊ฐ์ฒด๋ก export (const assertion ์ฌ์ฉ)
export const Icons = {
ArrowLeft,
Chart,
Comment,
Dashboard,
} as const;
// 3. ์์ด์ฝ ์ด๋ฆ ํ์
์๋ ์ถ๋ก
export type IconName = keyof typeof Icons;
์ฝ๋ ์์ธ ์ค๋ช
1. ๊ฐ๋ณ import
๊ฐ SVG ํ์ผ์ ๊ฐ๋ณ์ ์ผ๋ก importํฉ๋๋ค. @svgr/webpack์ด ์ด๋ค์ React ์ปดํฌ๋ํธ๋ก ๋ณํํฉ๋๋ค.
import ArrowLeft from './arrow_left.svg';
2. Icons ๊ฐ์ฒด with as const
export const Icons = {
ArrowLeft,
Chart,
// ...
} as const;
as const์ ์ญํ :
- TypeScript์๊ฒ ์ด ๊ฐ์ฒด์ ๊ฐ์ด ๋ณํ์ง ์์ ๊ฒ์์ ์๋ ค์ค๋๋ค
- ๊ฐ ํ๋กํผํฐ์ ํ์ ์ด ๋ ์ ํํ๊ฒ ์ถ๋ก ๋ฉ๋๋ค
IconNameํ์ ์ด ์ ํํ ๋ฆฌํฐ๋ด ํ์ ์ผ๋ก ์ถ๋ก ๋ฉ๋๋ค
// as const ์์ผ๋ฉด
type IconName = string // ๋๋ฌด ๊ด๋ฒ์
// as const ์์ผ๋ฉด
type IconName = "ArrowLeft" | "Chart" | "Comment" | "Dashboard" | ... // ์ ํ
3. IconName ํ์
export type IconName = keyof typeof Icons;
์ด๋ TypeScript์ ์ ํธ๋ฆฌํฐ ํ์ ์ ํ์ฉํ ๊ฒ์ ๋๋ค:
typeof Icons: Icons ๊ฐ์ฒด์ ํ์ ์ ๊ฐ์ ธ์ต๋๋คkeyof: ๊ฐ์ฒด์ ๋ชจ๋ ํค๋ฅผ ์ ๋์จ ํ์ ์ผ๋ก ๋ง๋ญ๋๋ค
๊ฒฐ๊ณผ:
type IconName = "ArrowLeft" | "Chart" | "Comment" | "Dashboard" | ...
ํ์ ์์ ์ฑ์ ์ฅ์
1. ์๋ ์์ฑ ์ง์
// IDE๊ฐ ์๋์ผ๋ก ์์ด์ฝ ๋ชฉ๋ก์ ๋ณด์ฌ์ค
const icon: IconName = 'ArrowLeft'; // ์๋์์ฑ!
2. ์ปดํ์ผ ํ์ ์๋ฌ ๊ฐ์ง
// ํ์
์๋ฌ: 'InvalidIcon'๋ IconName์ด ์๋
const icon: IconName = 'InvalidIcon';
// ํ์
์๋ฌ: ์กด์ฌํ์ง ์๋ ํค
const Icon = Icons['WrongIcon'];
3. ๋ฆฌํฉํ ๋ง ์์ ์ฑ
์์ด์ฝ ์ด๋ฆ์ ๋ณ๊ฒฝํ๋ฉด ์ฌ์ฉํ๋ ๋ชจ๋ ๊ณณ์์ TypeScript ์๋ฌ๊ฐ ๋ฐ์ํ์ฌ, ๋์น๋ ๋ถ๋ถ ์์ด ์์ ํ๊ฒ ๋ฆฌํฉํ ๋งํ ์ ์์ต๋๋ค.
์ค์ ์ฌ์ฉ ์์
๋ฐฉ๋ฒ 1: ์ง์ ์ฌ์ฉ
import { Icons } from '@/shared/assets/icons';
function MyComponent() {
return (
<div>
<Icons.ArrowLeft
width={20}
height={20}
fill="currentColor"
/>
<span>๋ค๋ก๊ฐ๊ธฐ</span>
</div>
);
}
๋ฐฉ๋ฒ 2: ๋์ ์ฌ์ฉ (ํ์ ์์ )
import { Icons, type IconName } from '@/shared/assets/icons';
interface NavItem {
icon: IconName; // ํ์
์์ !
title: string;
url: string;
}
const navItems: NavItem[] = [
{ icon: 'Dashboard', title: '๋์๋ณด๋', url: '/dashboard' },
{ icon: 'Chart', title: 'ํต๊ณ', url: '/statistics' },
// { icon: 'Wrong', ... } // ํ์
์๋ฌ
];
function Navigation() {
return (
<nav>
{navItems.map((item) => {
const IconComponent = Icons[item.icon]; // ํ์
์์
return (
<a key={item.url} href={item.url}>
<IconComponent width={20} height={20} />
<span>{item.title}</span>
</a>
);
})}
</nav>
);
}
์์ด์ฝ ์ถ๊ฐํ๊ธฐ
1๋จ๊ณ: SVG ํ์ผ ์ถ๊ฐ
src/shared/assets/icons/new-icon.svg
2๋จ๊ณ: index.ts์ ์ถ๊ฐ
// 1. import ์ถ๊ฐ
import NewIcon from './new-icon.svg';
// 2. Icons ๊ฐ์ฒด์ ์ถ๊ฐ
export const Icons = {
// ... ๊ธฐ์กด ์์ด์ฝ๋ค
NewIcon, // ์ฌ๊ธฐ์ ์ถ๊ฐ
} as const;
3๋จ๊ณ: ๋ฐ๋ก ์ฌ์ฉ
<Icons.NewIcon width={20} height={20} />
// ๋๋
const item: NavItem = {
icon: 'NewIcon', // ํ์
์ฒดํฌ
title: '์ ๊ธฐ๋ฅ',
url: '/new',
};
์ฅ๋จ์ ๋น๊ต
์ฅ์
- ํ์ ์์ ์ฑ: ์ปดํ์ผ ํ์์ ์คํ ๋ฐฉ์ง
- ์๋ ์์ฑ: IDE ์ง์์ผ๋ก ๊ฐ๋ฐ ์์ฐ์ฑ ํฅ์
- ๋ฒ๋ค ์ต์ ํ: ์ฌ์ฉํ๋ ์์ด์ฝ๋ง ๋ฒ๋ค์ ํฌํจ
- ์คํ์ผ ์ ์ด: CSS๋ก ์์, ํฌ๊ธฐ ์์ ๋กญ๊ฒ ๋ณ๊ฒฝ ๊ฐ๋ฅ
- ๊ด๋ฆฌ ์ฉ์ด: ์ค์ ์ง์ค์ ์์ด์ฝ ๊ด๋ฆฌ
- ๋ฆฌํฉํ ๋ง ์์ : ์ด๋ฆ ๋ณ๊ฒฝ ์ ๋ชจ๋ ์ฌ์ฉ์ฒ ์ถ์ ๊ฐ๋ฅ
๋จ์
- ์ด๊ธฐ ์ค์ : ์ค์ ์ด ๋ค์ ๋ณต์กํ ์ ์์
- ๋น๋ ์๊ฐ: SVG ๋ณํ์ผ๋ก ๋น๋ ์๊ฐ ์ฆ๊ฐ
๋ง์น๋ฉฐ
Next.js 16์ Turbopack์์ SVG๋ฅผ React ์ปดํฌ๋ํธ๋ก ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ๊ณผ, TypeScript๋ฅผ ํ์ฉํ ํ์ ์์ ํ ์์ด์ฝ ์์คํ ๊ตฌ์ถ ๋ฐฉ๋ฒ์ ์์๋ณด์์ต๋๋ค.
ํต์ฌ ํฌ์ธํธ๋ฅผ ์ ๋ฆฌํ๋ฉด
as: '*.js'์ต์ ์ Turbopack์์ ํ์์ ๋๋ค- **
as const**์ **keyof typeof**๋ก ํ์ ์์ ์ฑ์ ํ๋ณดํฉ๋๋ค - ์ค์ ์ง์ค์ ๊ด๋ฆฌ๋ก ์ ์ง๋ณด์๊ฐ ์ฌ์์ง๋๋ค
์ด ๋ฐฉ๋ฒ์ ์ฌ์ฉํ๋ฉด ์์ด์ฝ ๊ด๋ฆฌ์ ์์ฐ์ฑ๊ณผ ์์ ์ฑ์ ํฌ๊ฒ ํฅ์์ํฌ ์ ์์ต๋๋ค. ํนํ ๋๊ท๋ชจ ํ๋ก์ ํธ์์ ์ฌ๋ฌ ๊ฐ๋ฐ์๊ฐ ํ์ ํ ๋ ๋์ฑ ๋น์ ๋ฐํฉ๋๋ค.