๐Ÿšค ์„ฑ์žฅ์ผ์ง€ 7.0

์ฑ… ํ–‰๋ณตํ•œ ์ด๊ธฐ์ฃผ์˜์ž(์›จ์ธ ๋‹ค์ด์–ด)์˜ ๋‚ด์šฉ์— ์ž๊ทน๋ฐ›์•„ ์‹œ์ž‘ํ•˜๋Š” ์†Œ๋ฐ•ํ•œ ์„ฑ์žฅ๊ธฐ๋ก

์‚ด์•„์žˆ๋Š” ๊ฝƒ๊ณผ ์ฃฝ์€ ๊ฝƒ์€ ์–ด๋–ป๊ฒŒ ๊ตฌ๋ณ„ํ•˜๋Š”๊ฐ€?
์„ฑ์žฅํ•˜๊ณ  ์žˆ๋Š” ๊ฒƒ์ด ์‚ด์•„ ์žˆ๋Š” ๊ฒƒ์ด๋‹ค.
์ƒ๋ช…์˜ ์œ ์ผํ•œ ์ฆ๊ฑฐ๋Š” ์„ฑ์žฅ์ด๋‹ค!

โš› (7.0)<์™„์ „ ๊ฐœํŽธ> ํŒŒ์ธ๋งŒ ํ•™์Šต๋ฒ•์„ ์•Œ๊ฒŒ ๋œ๋งŒํผ, ์„ฑ์žฅ์ผ์ง€๋Š” ์ •๋ง ๊ทธ ๋‚ ์˜ ํ‚ค์›Œ๋“œ ์ค‘์‹ฌ์œผ๋กœ ๊ฐ„๋‹จํ•˜๊ฒŒ ์ •๋ฆฌํ•˜๋„๋ก ํ•œ๋‹ค.

Next.js์—์„œ์˜ svg

๋ณดํ†ต React๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด svg ํŒŒ์ผ์„ React Component๋กœ ๋ณ€ํ™˜ํ•ด์„œ ์‚ฌ์šฉํ•˜๊ณค ํ–ˆ๋‹ค.

import { ReactComponent as Logo } from './logo.svg';

// <Logo />

๊ทธ๋Ÿฐ๋ฐ Next.js์—์„œ๋Š” ์ด๋ ‡๊ฒŒ ์‚ฌ์šฉํ•˜๋ฉด ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. ์ด์œ ๋Š” Next.js์—์„œ๋Š” svg๋ฅผ ๋ชจ๋“ˆ๋กœ ์ธ์‹ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. Next.js์—์„œ svg๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ์—ฌ๋Ÿฌ๊ฐ€์ง€๊ฐ€ ์žˆ๋Š”๋ฐ ๊ทธ ์ค‘ 2๊ฐ€์ง€๋ฅผ ๊ธฐ๋กํ•ด๋‘”๋‹ค.

1. import ํ›„ Image ์ปดํฌ๋„ŒํŠธ ์‚ฌ์šฉ(๋น„์ถ”)

์ด ๋ฐฉ๋ฒ•์€ ์‚ฌ์‹ค ์ •๋ง ๊ฐ„๋‹จํ•˜๋‹ค. svg๋ฅผ importํ•˜๊ณ  Image ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค. ํ•˜์ง€๋งŒ ์ด ๋ฐฉ๋ฒ•์€ svg๋ฅผ React Component๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ๊ทธ๋ƒฅ ์ด๋ฏธ์ง€๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— svg์˜ ์žฅ์ ์„ ์‚ด๋ฆด ์ˆ˜ ์—†๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, svg์˜ ์ด๋ฏธ์ง€์˜ ํฌ๊ธฐ๋‚˜ ์ƒ‰์ƒ ๋“ฑ์„ props ํ˜น์€ css ํ˜•ํƒœ๋กœ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์—†๋‹ค. ๋˜ํ•œ Next.js ๊ณต์‹๋ฌธ์„œ์—๋„ ๋‚˜์™€์žˆ๋“ฏ์ด Image ์ปดํฌ๋„ŒํŠธ๋กœ์„œ์˜ ์ตœ์ ํ™”์— ์žˆ์–ด์„œ ํšจ์œจ์„ฑ์ด ๋–จ์–ด์ง„๋‹ค.

import Logo from './logo.svg';

<Image src={Logo} />;

2. @svgr/webpack ์‚ฌ์šฉ

์ด 2๋ฒˆ์งธ ๋ฐฉ๋ฒ•์ด ํ”ํžˆ ๊ถŒ์žฅ๋˜๋Š” ๋ฐฉ๋ฒ•์ธ๋ฐ, ๊ทธ ๋ฐฉ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค. @svgr/webpack์„ ์‚ฌ์šฉํ•˜๋ฉด svg๋ฅผ React Component๋กœ ๋ณ€ํ™˜ํ•ด์ฃผ๋Š” webpack loader๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด svg๋ฅผ React Component๋กœ ๋ณ€ํ™˜ํ•ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

๊ทธ ๊ตฌ์ฒด์ ์ธ ๋ฐฉ๋ฒ•์€ ์ฐธ๊ณ ์— ๋„ˆ๋ฌด ์ž˜๋‚˜์™€์žˆ์–ด์„œ ๊ทธ๋Œ€๋กœ ๋”ฐ๋ผํ•˜๋ฉด ๋œ๋‹ค.

๋‹ค๋งŒ, 2๊ฐ€์ง€ ์ฃผ์˜์ (ํ˜น์€ ํŒ)์ด ์žˆ๋‹ค.

  1. ์œ„์˜ ๋ฐฉ๋ฒ•๋Œ€๋กœ ํ–ˆ๋Š”๋ฐ, ๋ณ€ํ™˜๋œ svg component๊ฐ€ ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ์—์„œ๋Š” ์ž‘๋™ํ•˜์ง€ ์•Š๋Š” ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.(Module parse failed: Unexpected token) ์•„๋ž˜์™€ ๊ฐ™์ด ํ•˜๋ฉด ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค.
webpack(config) {
    // SVG ๊ฐ€์ ธ์˜ค๊ธฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ธฐ์กด ๊ทœ์น™์„ ๊ฐ€์ ธ์˜ค๊ธฐ
    const fileLoaderRule = config.module.rules.find(rule => rule.test?.test?.('.svg'));

    config.module.rules.push(
      // ?url๋กœ ๋๋‚˜๋Š” svg ๊ฐ€์ ธ์˜ค๊ธฐ์—๋งŒ ๊ธฐ์กด ๊ทœ์น™์„ ์ ์šฉ
      {
        ...fileLoaderRule,
        test: /\.svg$/i,
        resourceQuery: /url/, // *.svg?url
      },
      // ๋‹ค๋ฅธ ๋ชจ๋“  *.svg ๊ฐ€์ ธ์˜ค๊ธฐ๋ฅผ React ๊ตฌ์„ฑ ์š”์†Œ๋กœ ๋ณ€ํ™˜
      {
        test: /\.svg$/i,
        // issuer: /\.[jt]sx?$/, // *.svg๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ํŒŒ์ผ => ์ด ๋ถ€๋ถ„์„ ์‚ญ์ œํ•œ๋‹ค.
        resourceQuery: { not: /url/ }, // *.svg?url ์ œ์™ธ
        use: ['@svgr/webpack'],
      },
    );
    // svg์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ๋ฅผ ํ–ˆ์œผ๋ฏ€๋กœ *.svg๋ฅผ ๋ฌด์‹œํ•˜๋„๋ก ํŒŒ์ผ ๋กœ๋” ๊ทœ์น™์„ ์ˆ˜์ •
    fileLoaderRule.exclude = /\.svg$/i;

    return config;
  },

์œ„์— ์ฃผ์„์—๋„ ์žˆ๋“ฏ์ด issuer๋ฅผ ์‚ญ์ œํ•ด์•ผ ํ•œ๋‹ค. ์•„์ฃผ ์ •ํ™•ํ•œ ์ด์œ ๋Š” ์•„๋‹ˆ์ง€๋งŒ ์ฐพ์•„๋ณด๋‹ˆ issuer๊ฐ€ svg ํŒŒ์ผ์„ ๊ฐ€์ ธ์˜ค๋Š” ํŒŒ์ผ์„ ๋ช…์‹œํ•˜๋Š” ๊ฒƒ์ธ๋ฐ, ์‹ค์ œ ์„œ๋ฒ„์—์„œ ์‹คํ–‰๋˜๋Š” ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ๋Š” ๋นŒ๋“œ๋œ ํŒŒ์ผ์ด๊ธฐ ๋•Œ๋ฌธ์— ๋ช…์‹œ๋œ ํŒŒ์ผ์ด ์—†์–ด์„œ ๊ทธ๋Ÿฐ ๊ฒƒ ๊ฐ™๋‹ค.

(์ถ”๊ฐ€) ์ข€๋” ๋ฐฉ๋ฒ•์„ ์ฐพ์•„๋ณด๋˜ ์ค‘ ๋ฌธ์ œ ๊ด€๋ จ ๊นƒํ—™ ์ด์Šˆ ์ฝ”๋ฉ˜ํŠธ์—์„œ ์•„๋ž˜์™€ ๊ฐ™์ด ์ ์šฉํ•ด์„œ๋„ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

webpack(config, { isServer }) {
    // SVG ๊ฐ€์ ธ์˜ค๊ธฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ธฐ์กด ๊ทœ์น™์„ ๊ฐ€์ ธ์˜ค๊ธฐ
    const fileLoaderRule = config.module.rules.find(rule => rule.test?.test?.('.svg'));

    config.module.rules.push(
      // ?url๋กœ ๋๋‚˜๋Š” svg ๊ฐ€์ ธ์˜ค๊ธฐ์—๋งŒ ๊ธฐ์กด ๊ทœ์น™์„ ์ ์šฉ
      {
        ...fileLoaderRule,
        test: /\.svg$/i,
        resourceQuery: /url/, // *.svg?url
      },
      // ๋‹ค๋ฅธ ๋ชจ๋“  *.svg ๊ฐ€์ ธ์˜ค๊ธฐ๋ฅผ React ๊ตฌ์„ฑ ์š”์†Œ๋กœ ๋ณ€ํ™˜
      {
        test: /\.svg$/i,
        issuer: fileLoaderRule.issuer, // *.svg?url ์ œ์™ธ
        resourceQuery: { not: [...fileLoaderRule.resourceQuery.not, /url/] }, // *.svg?url ์ œ์™ธ
        use: ['@svgr/webpack'],
      },
    );
    // svg์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ๋ฅผ ํ–ˆ์œผ๋ฏ€๋กœ *.svg๋ฅผ ๋ฌด์‹œํ•˜๋„๋ก ํŒŒ์ผ ๋กœ๋” ๊ทœ์น™์„ ์ˆ˜์ •
    fileLoaderRule.exclude = /\.svg$/i;

    return config;
  },

๋ณด๋‹ˆ๊นŒ svg๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ํŒŒ์ผ์„ ๋ช…์‹œํ•  ๋•Œ, ์œ„์—์„œ ์ ์šฉํ•œ *.svg?url ๊ทœ์น™๊นŒ์ง€ ํฌํ•จ๋ผ์„œ ๋ช…์‹œ๋˜๋Š” ๊ฒƒ ๊ฐ™๋‹ค. ๊ทธ๋ž˜์„œ issuer๋ฅผ fileLoaderRule.issuer๋กœ ๋ช…์‹œํ•ด์ฃผ๋ฉด ํ•ด๊ฒฐ๋œ๋‹ค.

  1. svg component์— ๋Œ€ํ•œ custom ํƒ€์ž… ์ •์˜ํ•ด์ฃผ๊ธฐ

๊ธฐ๋ณธ์ ์œผ๋กœ ์œ„์˜ ๊ณผ์ •์„ ๊ฑฐ์ณ์„œ svg๋ฅผ react component์ฒ˜๋Ÿผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋ฉด ์ž˜ ์ž‘๋™ํ•œ๋‹ค! ๋‹ค๋งŒ ์•„์‰ฌ์šด ์ ์€ ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ์— ๋งˆ์šฐ์Šค๋ฅผ ์˜ฌ๋ฆฌ๋ฉด any ํƒ€์ž…์ด ๋‚˜์˜จ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค. ์ด ๋ถ€๋ถ„์€ ์ง์ ‘ ํƒ€์ž…์€ ์„ ์–ธํ•ด์คŒ์œผ๋กœ์จ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค. ๋‚˜๊ฐ™์€ ๊ฒฝ์šฐ root ๋””๋ ‰ํ† ๋ฆฌ์— svg-component.d.ts๋ฅผ ๋งŒ๋“ค์–ด์ฃผ์—ˆ๋‹ค.

declare module '*.svg' {
  import React from 'react';
  const svg: React.FC<React.SVGProps<SVGSVGElement>>;
  export default svg;
}

๊ทธ๋ฆฌ๊ณ  tsconfig.json์— ์•„๋ž˜์™€ ๊ฐ™์ด ์ถ”๊ฐ€ํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

{
  "compilerOptions": {
    "include": ["svg-component.d.ts", "next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"]
  }
}

์ด์ œ ๋งˆ์šฐ์Šค๋ฅผ ์˜ฌ๋ ค๋ณด๋ฉด React.FC<React.SVGProps<SVGSVGElement>> ํƒ€์ž…์ด ์ž˜ ๋‚˜์˜ค๋Š” ๊ฑธ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.(๊ทธ๋ž˜๋„ any๊ฐ€ ๋‚˜์˜ค๋ฉด vscode ์žฌ์‹œ์ž‘ํ•ด๋ณด๊ธฐ~!)

๐Ÿ“ ํšŒ๊ณ 

์˜ค๋Š˜ ํ•˜๋ฃจ๊ฐ€ ๋ญ”๊ฐ€ ํ›„์šฐ์›…ํ•˜๊ณ  ์ง€๋‚˜๊ฐ”๋‹คโ€ฆ ์ •์‹  ๋ฐ”์ง ์ฐจ๋ฆฌ๊ณ  ์‚ด์•„์•ผ๊ฒ ๋‹ค์ž‰!!! ์‹œ๊ฐ„์„ ์ธ์ง€ํ•˜๋ฉด์„œ ์‚ด์ž!

์ฐธ๊ณ