๋ฐ˜์‘ํ˜•

์œ„์น˜๋ณ„ ํŒŒ์ผ ๊ฒฝ๋กœ ๊ธฐ์ค€


  • JSX ์—˜๋ฆฌ๋จผํŠธ ์†์„ฑ์˜ ๋ฃจํŠธ ํด๋” ๊ธฐ์ค€ : public
  • CSS ํŒŒ์ผ ๋ฃจํŠธ ํด๋” ๊ธฐ์ค€ : src
  • ํŒŒ์ผ ์ตœ์ƒ๋‹จ import ๊ตฌ๋ฌธ : src ํด๋”๋งŒ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ(public ํด๋”์— ์žˆ๋Š” ํŒŒ์ผ์€ ๋ถˆ๊ฐ€)
  • <img> ํƒœ๊ทธ src ์†์„ฑ(JSX ํƒœ๊ทธ ์†์„ฑ)์— src ํด๋”์— ์žˆ๋Š” ์ด๋ฏธ์ง€ ํŒŒ์ผ์„ ์ง€์ •ํ•˜๋ ค๋ฉด…
    • ํŒŒ์ผ ์ตœ์ƒ๋‹จ์—์„œ ๋ถˆ๋Ÿฌ์˜จ ์ด๋ฏธ์ง€ ํŒŒ์ผ์„ src ์†์„ฑ์— ํ• ๋‹นํ•˜๊ฑฐ๋‚˜,
    • src ์†์„ฑ ์•ˆ์—์„œ require() ์‚ฌ์šฉ e.g. <img src={require('...').default} />

 

๋ฌธ์ œ ์ƒํ™ฉ


 

JSX ์—˜๋ฆฌ๋จผํŠธ ์†์„ฑ์˜ ๋ฃจํŠธ ํด๋” ๊ธฐ์ค€์€ public ํด๋”๋ฏ€๋กœ ์ด๋ฏธ์ง€ ํŒŒ์ผ์ด src ํด๋”์— ์žˆ๋‹ค๋ฉด ํŒŒ์ผ ์ตœ์ƒ๋‹จ์—์„œ ์ด๋ฏธ์ง€ ํŒŒ์ผ์„ import ํ•œ ํ›„ ์ธ๋ผ์ธ ์Šคํƒ€์ผ url() ํ•จ์ˆ˜์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. *.png, *.webp ๊ฐ™์€ ์ด๋ฏธ์ง€ ํŒŒ์ผ์€ ์•„๋ž˜ ๋ฐฉ๋ฒ•์œผ๋กœ ์ž˜ ์ž‘๋™ํ•œ๋‹ค.

// src/assets/image ํด๋”์— ์žˆ๋Š” profile-image.png ํŒŒ์ผ import
import ArrowDown from '../assets/image/arrow-down.png';

// ์ปดํฌ๋„ŒํŠธ ๋ฆฌํ„ด๋ฌธ
<select
  style={{ backgroundImage: `url(${ArrowDown})` }}
  className="bg-no-repeat bg-right pr-4"
>
  ...
</select>;

 

ํ•˜์ง€๋งŒ ์œ„์™€ ๋™์ผํ•œ ๋ฐฉ๋ฒ•์œผ๋กœ .svg ํŒŒ์ผ์— ์ ์šฉํ•ด๋ณด๋ฉด ์ด๋ฏธ์ง€๊ฐ€ ์•ˆ๋‚˜์˜จ๋‹ค. ๐Ÿ˜ข

 

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•


ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์€ 2๊ฐ€์ง€๊ฐ€ ์žˆ๋‹ค.

  1. svg ํŒŒ์ผ์„ Data URI์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜
  2. CSS ํŒŒ์ผ์— ์ปค์Šคํ…€ ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค๊ณ  url() ํ•จ์ˆ˜์— svg ํŒŒ์ผ ๊ฒฝ๋กœ ๋ช…์‹œ (SSR / SSG์—์„  ์•ˆ๋ ์ˆ˜๋„ ์žˆ์Œ)

 

๋ฐฉ๋ฒ• 1 — Data URI ์‚ฌ์šฉ

๐Ÿ’ก ์ด๋ฏธ์ง€ ํŒŒ์ผ์„ base64 URI๋กœ ๋ฐ”๊ฟ”์ฃผ๋Š” url-loader ๊ฐ™์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋„ ์žˆ๋‹ค.

 

svg ํŒŒ์ผ์„ Data URI์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ํ•˜๋ฉด CSS url() ํ•จ์ˆ˜์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. url() ํ•จ์ˆ˜ ์ธ์ž์—” ์ด๋ฏธ์ง€ ํŒŒ์ผ์˜ ์ƒ๋Œ€ / ์ ˆ๋Œ€ ๊ฒฝ๋กœ, ์›น ๋ฆฌ์†Œ์Šค ์ฃผ์†Œ ํ˜น์€ data URL / blob URL์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค.

import ArrowDown from '../assets/image/arrow-down.svg';
import { renderToStaticMarkup } from 'react-dom/server';

// ์ปดํฌ๋„ŒํŠธ ๋ณธ๋ฌธ
// svg ํŒŒ์ผ์„ Data URI์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜
const svgString = encodeURIComponent(renderToStaticMarkup(<ArrowDown />));

// ์ปดํฌ๋„ŒํŠธ ๋ฆฌํ„ด๋ฌธ
<select style={{ backgroundImage: `url("data:image/svg+xml,${svgString}")` }}>
  ...
</select>;

 

Percent Encoding

URI์—์„œ ๋ฌธ๋ฒ•์  ์˜๋ฏธ๋ฅผ ๊ฐ–๋Š” ๋ฌธ์ž๋ฅผ ์˜ˆ์•ฝ ๋ฌธ์ž๋ผ๊ณ  ํ•œ๋‹ค. ์˜ˆ๋ฅผ๋“ค์–ด URI์—์„œ & ์•ฐํผ์ƒŒ๋“œ๋Š” AND ์˜๋ฏธ๋ฅผ ๊ฐ–๋Š”๋‹ค. ๋งŒ์•ฝ ์ฟผ๋ฆฌ์ŠคํŠธ๋ง(?key=value) value ๊ฐ’์— & ๊ฐ™์€ ์˜ˆ์•ฝ ๋ฌธ์ž๋ฅผ ๋ฌธ๋ฒ•์  ์˜๋ฏธ๊ฐ€ ์•„๋‹Œ ๋ฌธ์ž ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ์ธ์ฝ”๋”ฉ(์ด์Šค์ผ€์ดํ”„)์ด ํ•„์š”ํ•˜๋‹ค.

 

๐Ÿ”๏ธ ์˜ˆ์•ฝ ๋ฌธ์ž ๋ชฉ๋ก — URI์—์„œ ๋ฌธ๋ฒ•์  ์˜๋ฏธ๋ฅผ ๊ฐ€์ง / ๋ฌธ์ž ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ์ธ์ฝ”๋”ฉ ํ•„์š”

!   *   '   (   )   ;   :   @   &   =   +   $   ,   /   ?   #   [   ]

 

๐Ÿ”๏ธ ๋น„์˜ˆ์•ฝ ๋ฌธ์ž ๋ชฉ๋ก — ์ธ์ฝ”๋”ฉ ํ•„์š”ํ•˜์ง€ ์•Š์Œ

A-Z   a-z   0-9   -   _   .   ~

 

ํ•œ๊ธ€, ํ•œ์ž ๊ฐ™์€ non-ASCII ๋ฌธ์ž๋ฅผ URI์—์„œ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์ธ์ฝ”๋”ฉ์ด ํ•„์š”ํ•˜๋‹ค. ์ด์ฒ˜๋Ÿผ URI์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋Š” ๋ฌธ์ž์—ด์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก(ํ˜น์€ ์˜ˆ์•ฝ ๋ฌธ์ž๋ฅผ ๋ฌธ๋ฒ•์  ์˜๋ฏธ๊ฐ€ ์•„๋‹Œ ๋ฌธ์ž ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•  ๋•Œ) ๋ณ€ํ™˜ํ•˜๋Š” ๊ณผ์ •์„ Percent Encoding(URL Encoding)์ด๋ผ๊ณ  ๋ถ€๋ฅธ๋‹ค.

 

Percent Encoding ์ธ์ฝ”๋”ฉํ•œ ๋ฌธ์ž๋Š” % ๋กœ ์‹œ์ž‘ํ•˜๊ณ  ๊ทธ๋’ค์— 16์ง„์ˆ˜ ์ˆซ์ž๋ฅผ ๋ถ™์—ฌ์„œ ํ‘œํ˜„ํ•œ๋‹ค. e.g. &%26

 

encodeURIComponent / encodeURI

encodeURIComponent ๋ฉ”์„œ๋“œ๋Š” ๋ฌธ์ž์—ด์„ UTF-8๋กœ ์ด์Šค์ผ€์ดํ”„ํ•œ ๋ฌธ์ž์—ด์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ์ฆ‰ URI์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก Percent Encodingํ•œ ๋ฌธ์ž์—ด์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ์ด์™€ ๋น„์Šทํ•œ encodeURI ๋ฉ”์„œ๋“œ๋„ ์žˆ๋‹ค. ์ด ๋‘˜ ๋ฉ”์„œ๋“œ์˜ ์ˆ˜ํ–‰ ๊ธฐ๋Šฅ์€ ๋™์ผํ•˜์ง€๋งŒ ์ด์Šค์ผ€์ดํ”„ํ•˜์ง€ ์•Š๋Š” ๋ฌธ์ž์—ด์— ์ฐจ์ด๊ฐ€ ์žˆ๋‹ค.

const music = encodeURIComponent('Rock&Roll'); // 'Rock%26Roll'
const url = `https://google.com/search?q=${music}`;

 

  • encodeURIComponent : ์ฃผ๋กœ ์ฟผ๋ฆฌ์ŠคํŠธ๋ง(?key=value)์˜ value ๋ถ€๋ถ„์„ ์ธ์ฝ”๋”ฉํ•  ๋•Œ ์‚ฌ์šฉ
    • ์ œ์™ธ ๋ฌธ์ž : A-Z  a-z  0-9  -  _  .  !  ~  *  '  (  )
    • ๋””์ฝ”๋”ฉ ๋ฉ”์„œ๋“œ : decodeURIComponent
  • encodeURI : URI ์ „์ฒด๋ฅผ ์ธ์ฝ”๋”ฉํ•  ๋•Œ ์‚ฌ์šฉ (URI๋ฅผ ๊ตฌ์„ฑํ•˜๋Š”๋ฐ ํ•„์š”ํ•œ ์˜ˆ์•ฝ/๋น„์˜ˆ์•ฝ ๋ฌธ์ž๋Š” ์ธ์ฝ”๋”ฉ ์•ˆํ•จ)
    • ์ œ์™ธ ๋ฌธ์ž : A-Z  a-z  0-9  ;  ,  /  ?  :  @  &  =  +  $  -  _  .  !  ~  *  '  (  )  #
    • ๋””์ฝ”๋”ฉ ๋ฉ”์„œ๋“œ : decodeURI

 

renderToStaticMarkup

React ์—˜๋ฆฌ๋จผํŠธ์— ๋Œ€ํ•ด ์ •์  HTML(HTML ๋ฌธ์ž์—ด)์„ ์ƒ์„ฑํ•˜๋Š” ๋ฉ”์„œ๋“œ. data-reactroot ์ฒ˜๋Ÿผ ๋ฆฌ์•กํŠธ ๋‚ด๋ถ€์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” DOM ์–ดํŠธ๋ฆฌ๋ทฐํŠธ(data-*)๋Š” ๋งŒ๋“ค์ง€ ์•Š๋Š”๋‹ค. ๋”ฐ๋ผ์„œ ReactDOM.hydrate()๋ฅผ ์‚ฌ์šฉํ•ด ์ƒํ˜ธ์ž‘์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ด๋ฒคํŠธ๋ฅผ ์ฃผ์ž…ํ•  ์ˆ˜ ์—†๋‹ค.

const staticMarkup = renderToStaticMarkup(<h1>hello world</h1>);
console.log(staticMarkup); // <h1>hello world</h1>
console.log(typeof staticMarkup); // string

 

Data URI

data:[<mime type>][;charset=<charset>][;base64],<encoded data>

 

Data URI ์Šคํ‚ด(scheme)์€ data: ์ ‘๋‘์‚ฌ๊ฐ€ ๋ถ™์€ ๋ฌธ์ž์—ด๋กœ, ํŒŒ์ผ์„ ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ํ•œ ๊ฒƒ์œผ๋กœ ์ƒ๊ฐํ•˜๋ฉด ๋œ๋‹ค. ์ž‘์€ ์ด๋ฏธ์ง€ ๋“ฑ์˜ ํŒŒ์ผ์„ ๋ฌธ์„œ์— ์ธ๋ผ์ธ์œผ๋กœ ๋„ฃ๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•œ๋‹ค. ์ด๋ฏธ ๋ฌธ์„œ์— ํฌํ•จํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ์„œ๋ฒ„์— ์š”์ฒญํ•˜์ง€ ์•Š๊ณ  ํŒŒ์ผ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. Data URI๋Š” ์ด๋ฏธ์ง€ ํƒœ๊ทธ์˜ src ์†์„ฑ์ด๋‚˜ CSS ์†์„ฑ์˜ url ํ•จ์ˆ˜์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

{ backgroundImage: `url("data:image/svg+xml,${svgString}")` }

 

๋ฐ์ดํ„ฐ๊ฐ€ ํ…์ŠคํŠธ ํ˜•์‹์ด๋ฉด(React์—์„œ ์ œ๊ณตํ•˜๋Š” renderToStaticMarkup ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด React ์—˜๋ฆฌ๋จผํŠธ → HTML ๋ฌธ์ž์—ด๋กœ ๋ณ€๊ฒฝ), ๋ฐ์ดํ„ฐ๋ฅผ ์ด์Šค์ผ€์ดํ”„ํ•œ ํ›„(encodeURIComponent ๋ฉ”์„œ๋“œ ์‚ฌ์šฉ) MIME ํƒ€์ž…์— ๋งž์ถฐ์„œ ๋ฐ”๋กœ ์ž„๋ฒ ๋“œํ•  ์ˆ˜ ์žˆ๋‹ค. *.svg ํŒŒ์ผ์˜ MIME ํƒ€์ž…์€ image/svg+xml ์ด๋‹ค.

 

๐Ÿ’ก ๋ฐ์ดํ„ฐ ํƒ€์ž…์„ ์ƒ๋žตํ•˜๋ฉด text/plain;charset=US-ASCII ์„ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์‚ฌ์šฉํ•œ๋‹ค.

 

๋ฐ์ดํ„ฐ๊ฐ€ ์ด๋ฏธ์ง€ ํŒŒ์ผ์ด๋ผ๋ฉด base64(๋ฐ์ดํ„ฐ๋ฅผ 64์ง„๋ฒ•์œผ๋กœ ๋‚˜ํƒ€๋‚ธ ๊ฒƒ)๋กœ ์ธ์ฝ”๋”ฉํ•œ ์ด์ง„ ๋ฐ์ดํ„ฐ๋ฅผ ์ž„๋ฒ ๋“œํ•  ์ˆ˜ ์žˆ๋‹ค(๋ฌผ๋ก  ์ผ๋ฐ˜ ํ…์ŠคํŠธ๋„ base64๋กœ ์ธ์ฝ”๋”ฉํ•  ์ˆ˜ ์žˆ๋‹ค). ๋ฐ”์ด๋„ˆ๋ฆฌ ๋ฐ์ดํ„ฐ๋ฅผ base64๋กœ ์ธ์ฝ”๋”ฉํ•ด์„œ ํ…์ŠคํŠธ๋กœ ๋‹ค๋ฃฐ ์ˆ˜ ์žˆ์ง€๋งŒ ์›๋ณธ๋ณด๋‹ค ์•ฝ 33% ์ •๋„ ํฌ๊ธฐ๊ฐ€ ๋Š˜์–ด๋‚˜๋Š” ๋‹จ์ ์ด ์žˆ๋‹ค. ๋”ฐ๋ผ์„œ ํฐ ์ด๋ฏธ์ง€์— ์‚ฌ์šฉํ•˜๊ธฐ์—” ์ ํ•ฉํ•˜์ง€ ์•Š๋‹ค.

// ์ด๋ฏธ์ง€๋ฅผ Data URI๋กœ ๋ณ€ํ•œํ•˜๋ฉด ์•„๋ž˜์ฒ˜๋Ÿผ ๋‚˜์˜จ๋‹ค
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA////sViHsVR/rSQDsVyLRwD++ffuUBL//Pv97+vrT...';
๋”๋ณด๊ธฐ
์ฝ”๋“œ ์ฐธ๊ณ  MDN

 

Hello World! ๋ฌธ์ž์—ด์˜ text/plain ๋ฐ์ดํ„ฐ โ–ผ

data:,Hello%2C%20World!

 

Hello World! ๋ฌธ์ž์—ด์˜ base64 ์ธ์ฝ”๋”ฉ ๋ฒ„์ „ โ–ผ

data:text/plain;base64,SGVsbG8sIFdvcmxkIQ%3D%3D

 

<h1>Hello, World!</h1>์ธ HTML ๋ฌธ์„œ โ–ผ

data:text/html,%3Ch1%3EHello%2C%20World!%3C%2Fh1%3E

 

์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ alert ์ฐฝ์„ ์‹คํ–‰ํ•˜๋Š” HTML ๋ฌธ์„œ โ–ผ

data:text/html,<script>alert('hi');</script>

 

๋ฐฉ๋ฒ• 2 — CSS ํŒŒ์ผ์— ์ปค์Šคํ…€ ํด๋ž˜์Šค ์ •์˜

๐Ÿ’ก NextJS(SSG / SSR)์—์„  ์ƒํ™ฉ์— ๋”ฐ๋ผ ์•„๋ž˜ ๋ฐฉ๋ฒ• ์•ˆ๋ ์ˆ˜๋„ ์žˆ์œผ๋‹ˆ ์ฐธ๊ณ .

 

CSS ํŒŒ์ผ์—์„œ ์ปค์Šคํ…€ ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“  ํ›„ background-image ์†์„ฑ url() ํ•จ์ˆ˜์— svg ํŒŒ์ผ ๊ฒฝ๋กœ๋ฅผ ๋ช…์‹œํ•œ๋‹ค. ๊ทธ๋Ÿฐ ํ›„ ํ•„์š”ํ•œ ์ปดํฌ๋„ŒํŠธ์—์„œ class๋ฅผ ์ถ”๊ฐ€ํ•ด์„œ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

.bg-arrow-down {
  background-image: url('../assets/image/arrow-down.svg') no-repeat right center; /* no-repeat 100% 50% */
}
// ์ปดํฌ๋„ŒํŠธ return๋ฌธ
<select className="bg-arrow-down pr-4">...</select>;

 

๋ ˆํผ๋Ÿฐ์Šค


 


๊ธ€ ์ˆ˜์ •์‚ฌํ•ญ์€ ๋…ธ์…˜ ํŽ˜์ด์ง€์— ๊ฐ€์žฅ ๋น ๋ฅด๊ฒŒ ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค. ๋งํฌ๋ฅผ ์ฐธ๊ณ ํ•ด ์ฃผ์„ธ์š”
๋ฐ˜์‘ํ˜•