카테고리 없음

svg 사용하기 @svgr/webpack

합주기 2025. 5. 1. 02:24

보통 next에서는 svg 파일을 사용하기 위해서는 @svgr/webpack 을 설치한다.

import Component from './star.svg' -> svg를 컴포넌트로 import하기
import url from './star.svg?url' -> svg를 url로 import하기

 
 

1. svg를 컴포넌트로 import 하기

webpack.config.js 혹은 next.config.js 에 @svgr/webpack 을 사용한다고 명시한다.

/**next.config.ts*/

module.exports = {
  module: {
    rules: [
      {
        test: /\.svg$/i,
        issuer: /\.[jt]sx?$/,
        use: ['@svgr/webpack'],
      },
    ],
  },
}

 
사용하고 싶은 컴포넌트에서 svg 파일을 import해서 사용한다.

import Star from './star.svg'

const Example = () => (
  <div>
    <Star />
  </div>
)

 

2. svg를 url로 import하기

만약 svg를 url로써 활용하고 싶다면(컴포넌트가 아님), 가장 쉬운 방법은 resourceQuery를 활용하는것이다.
이전에 작성한 next.config.js 파일에 resourceQuery를 활용해서 “.svg?url”로 끝날 경우에는 문자열로 변환하도록 한다.
 

/**next.config.ts*/

module.exports = {
  module: {
    rules: [
      {
        test: /\.svg$/i,
        type: 'asset',
        resourceQuery: /url/,// *.svg?url
      },
      {
        test: /\.svg$/i,
        issuer: /\.[jt]sx?$/,
        resourceQuery: { not: [/url/] },// exclude react component if *.svg?url
        use: ['@svgr/webpack'],
      },
    ],
  },
}

 
이걸 다음과 같이 활용할 수 있다.

import svg from './assets/file.svg?url'
import Svg from './assets/file.svg'
const App = () => {
  return (
    <div>
      <img src={svg} width="200" height="200" />
      <Svg width="200" height="200" viewBox="0 0 3500 3500" />
    </div>
  )
}

 

3. 프로젝트에 적용하기

이걸 프로젝트에 적용하게 된 계기이다.
하단 네비게이션바(GNB)를 구현했다.
여기서 NAV_ITEMS에 4가지(홈, 모임, 랭킹, 프로필)을 저장하고, 배열을 순회하며 4가지 아이템이 고르게 배치되도록 했다.
 
하지만 마음에 들지 않는 부분이 있었다.
NAV_ITEMS를 별도 constants 파일로 깔끔하게 분리하고 싶었다.
하지만 import 방식, 즉 JSX 구문을 활용하고 있어 constants 확장자가 constants.tsx로 규칙(.tsx) 에 깨지는 게 마음에 들지 않았다.

"use client";

import Link from "next/link";
import { usePathname } from "next/navigation";
import GatheringIcon from "/public/nav/group02.svg";
import HomeIcon from "/public/nav/home.svg";
import RankingIcon from "/public/nav/medal02.svg";
import ProfileIcon from "/public/nav/profile.svg";

const NAV_ITEMS = [
  {
    label: "홈",
    icon: <HomeIcon />,
    href: `/home`,
  },
  {
    label: "모임",
    icon: <GatheringIcon />,
    href: `/gatherings`,
  },
  {
    label: "랭킹",
    icon: <RankingIcon />,
    href: `/ranking`,
  },
  {
    label: "프로필",
    icon: <ProfileIcon />,
    href: `/profile`,
  },
];

const NAV_ITEM_STYLE = {
  default: "text-grey-400",
  active: "text-green",
};

export default function Nav() {
  const pathname = usePathname();
  const pathnameFirst = pathname.split("/")[1];

  return (
    <div className="shadow-[0px_4px_24px_0px_rgba(170,170,170,0.30)]justify-center fixed bottom-0 mx-auto flex w-96 w-full max-w-[500px] items-end gap-16 bg-white px-10 pt-2.5 pb-5">
      {NAV_ITEMS.map((item, index) => (
        <Link
          key={index}
          href={item.href}
          className={`flex flex-1 flex-col items-center justify-center gap-0.5 ${item.href === `/${pathnameFirst}` ? NAV_ITEM_STYLE.active : NAV_ITEM_STYLE.default}`}
        >
          {item.icon}
          <div className="text-body3-medium">{item.label}</div>
        </Link>
      ))}
    </div>
  );
}

 
 
따라서 다음과 같이 수정했다.
이전에 설명한 "svg를 url로 import하기" 처럼 next.config.ts를 수정했다.
그다음 상수 파일로 분리하였다.
 

/** constants/nav.ts */

import homeUrl from "@/assets/icons/home.svg?url";
import gatherUrl from "/public/nav/group02.svg?url";
import rankingUrl from "/public/nav/medal02.svg?url";
import profileUrl from "/public/nav/profile.svg?url";

export const NAV_ITEMS = [
  {
    label: "홈",
    icon: homeUrl,
    href: `/home`,
  },
  {
    label: "모임",
    icon: gatherUrl,
    href: `/gatherings`,
  },
  {
    label: "랭킹",
    icon: rankingUrl,
    href: `/ranking`,
  },
  {
    label: "프로필",
    icon: profileUrl,
    href: `/profile`,
  },
];

 
svg 타입 선업하기
typescript에서 svg 파일을 url 형태로 import 시키기 위해선 타입 선언이 필요하므로, 이 부분도 추가했다.

/** types/svg.d.ts */

declare module "*.svg?url" {
  const content: string;
  export default content;
}

declare module "*.svg" {
  import { FC, SVGProps } from "react";
  const content: FC<SVGProps<SVGElement>>;
  export default content;
}

 
이로써 관심사별로 분리하여 코드는 깔끔해졌다.
하지만 어이없게도 기존 코드로 돌아갈 수 밖에 없었다.
SVG 색상 변경이 어려웠기 때문이였다.
 
변경 이후(URL import)

// src/constants/nav.ts
import homeUrl from "@/assets/icons/home.svg?url";

export const NAV_ITEMS = [
  {
    label: "홈",
    icon: homeUrl,  // URL string
    href: `/home`,
  },
  // ...
];

// src/components/organisms/Nav.tsx
<Image src={item.icon} />  // SVG를 이미지로 표시

 
👉 장점: 상수 파일을 .ts로 유지 가능
👉 단점: SVG 색상 변경이 어려움
 
변경 이전(컴포넌트 import 방식)

import GatheringIcon from "@/assets/icons/group02.svg";
import HomeIcon from "@/assets/icons/home.svg";
import RankingIcon from "@/assets/icons/medal02.svg";
import ProfileIcon from "@/assets/icons/profile.svg";
import Link from "next/link";
import { usePathname } from "next/navigation";

const NAV_ITEMS = [
  {
    label: "홈",
    icon: <HomeIcon />,
    href: `/home`,
  },
  {
    label: "모임",
    icon: <GatheringIcon />,
    href: `/gatherings`,
  },
  {
    label: "랭킹",
    icon: <RankingIcon />,
    href: `/ranking`,
  },
  {
    label: "프로필",
    icon: <ProfileIcon />,
    href: `/profile`,
  },
];

👉 장점: SVG 색상을 className으로 쉽게 변경 가능
👉 단점: 확장자가 .tsx 여야함
 
결론
svg 파일을 import 해오는 방법 2가지
- 컴포넌트 형태로 가져오기 => .svg
- url 형태로 가져오기 -> .svg?url
 
그렇다면 어떤 기준으로 나누어 작성할까?
 
.svg?url
- 만약 svg 파일 여러개를 constants.ts에 저장하고 싶을 때 활용
- 단 동적으로 색상, 크기 변경해야할 때는 사용하지 않는다.
예) 프로필 이미지가 없을 때, "N개 중 랜덤한 이미지를 보여주기 위해" 
 
- svg
- 한개만 사용하는 경우
- 동적으로 색상, 크기 변경하는 경우