React와 Webpack 환경의 프로젝트입니다. (CRA O)
개발 환경에서는 문제 없었던 이미지 파일들이 배포 환경에서 제대로 렌더링되지 않는 문제를 해결해본다.
개발 환경 | 배포 환경 |
---|---|
![]() | ![]() |
기존 코드를 살펴보면 다음과 같다.
아이콘 svg 파일을 모두 src/assets
디렉토리에 esModule로 import 하고 묶어서 reexport 했다.
// src/assets/icons
import cancel from './cancel.svg';
import success from './check-circle.svg';
import info from './info-circle.svg';
import warning from './exclamation-triangle.svg';
import error from './times-circle.svg';
export { cancel, success, info, warning, error };
Notification 컴포넌트에서 모든 아이콘 파일들을 import한 icon 객체로 접근하고자 했다.
cancel 아이콘은 단일로 가져오고 success, info, warning, error 아이콘들은 알림 유형에 따라 동적으로 모듈을 가져오고 있다.
// src/notification.js
import * as icon from '../../assets/toast-notification/icons';
const Notification = ({type}) => {
(...)
return (
<>
(...)
<img src=${icon[type]}/>
<img src=${icon.cancel} />
</>
}
네트워크 탭을 살펴보면 다음과 같이 경로로 모듈을 dynamic import 한 이미지들은 모두 500 에러가 발생한다.
여기서 정상적으로 import 된 svg 파일과는 달리 오류가 발생하는 파일은 파일 유형이 svg+xml
아닌 text/html
유형으로 전송되고 있다.
일반적으로 이미지 파일은 /public
또는 src/assets
디렉토리에 저장한다.
/public
정적 파일을 넣는 디렉토리다.
정적 파일이란 html와 이미지 같은 정적자원들을 의미한다.
모듈 시스템 밖에서 자원을 추가하는 방식으로, public
디렉토리 안에 있는 파일들은 webpack이 처리하지 않으므로 원본 그대로 복사본을 build
폴더에 복사한다.
public
디렉토리에 있는 자원을 참조하려면 PUBLIC_URL
환경 변수를 사용한다.
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
render() {
return <img src={process.env.PUBLIC_URL + '/img/logo.png'} />;
}
public
디렉토리에 있는 파일들은 빌드 프로세스에 의해 처리되지 않으며, 서버가 이 파일들을 복사본으로 그대로 제공한다./src/assets
작업하는 파일들을 넣는 디렉토리로 주로 js파일, css 파일들이 들어있다.
JS파일 내에서 stylesheet, 이미지, 폰트를 추가할 때 모듈로서 import
하는 것을 권장한다.
import cancel from './cancel.svg';
function App(){
return(
<img src={cancel} alt='icon' />
);
}
export default App
import()
함수를 사용하면 비동기적으로 모듈을 로드할 수 있다.const NotFound = lazy(() => import('@/pages/NotFound'));
lazy()
함수를 이용해 해당 컴포넌트가 실제로 필요한 시점에 로드하고import()
를 통해 비동기적으로 로드한다. <img src={require('../assets/cancel.svg').default}
process.env.PUBLIC_URL
process.env.PUBLIC_URL
은 빌드시 public 디렉토리의 절대 경로를 나타낸다.
package.json
의 homepage
속성값으로 설정할 수 있다.
즉, 개발 환경에서는 빈 값, 배포 환경에서는 homepage의 속성 값을 나타내고 값이 달라질 수 있다.
다음은 public 디렉토리 경로를 .
으로 설정한 경우다. (homepage: '.'
)
첫번째 케이스에서 경로가 달라지게 되므로 두번째 케이스인 /img.svg
는 루트 경로가 변했으므로 deploy 배포 환경에서 이미지가 로드되지 않는다.
이러한 점을 고려해 process.env.PUBLIC_URL
변수를 사용해서 경로 설정을 해야 가장 정확하고 오류가 생기지 않을 것임을 알 수 있다.
<img src=""> | development | deploy |
---|---|---|
process.env.PUBLIC_URL + '/img.svg' | /img.svg | ./img.svg |
/img.svg | /img.svg | /img.svg |
img.svg | img.svg | img.svg |
./img.svg | ./img.svg | ./img.svg |
여기서 잠시 경로에 대해서 짚어보면
경로는절대 경로
와상대 경로
로 나뉜다.
절대 경로
:root
부터 해당 파일까지의 전체 경로(URL)을 의미하며, 어느 곳에서든 경로에 접근할 수 있다.상대 경로
: 현재 파일의 위치를 기준으로 한다.
/
:root
./
: 현재 위치../
: 상위 경로
src 디렉토리에 있는 이미지를 import 및 require 구문을 통해 모듈로서 가져왔을 때는 개발환경과 배포환경에서 모두 같은 경로로 인식했다.
이때 svg 파일은 해시값으로 가져오고, svg 파일을 제외한 jpg, png 파일 등은 1000 byte 미만시 date url 형태로 더욱 빠른 파일 전송을 위한 유형으로서 가져온다.
<img src=""> | development | deploy |
---|---|---|
import img from './img.svg' 으로 최상단에서 import 한 후, img를 src에 넣는다. | ./static/media/img.681ba9d...svg | ./static/media/img.681ba9d...svg |
require('./cancel.svg').default | ./static/media/img.681ba9d...svg | ./static/media/img.681ba9d...svg |
📦public
┣ 📂assets
┃ ┗ 📜img.svg
┗ 📜index.html
PUBLIC_URL
환경 변수를 사용해서 절대경로를 사용할 수 있다. const App = () => {
return <img src={process.env.PUBLIC_URL + '/assets/img.svg'} alt={'public-img'} />
}
📦src
┣ 📂assets
┃ ┗ 📜img.svg
┣ 📜index.tsx
┗ 📜App.tsx
src 디렉토리 내의 파일들은 webpack이 모듈로서 처리하므로 번들링 및 경량화를 통해 네트워크 부하를 줄일 수 있다.
컴파일 단계에서 에러를 잡을 수 있다.
import
또는 require
로 모듈을 불러올 수 있다.
import
는 webpack의 tree shaking 을 사용해 사용하지 않는 코드를 제거하여 성능이 개선된다. (권장)
import Img from './src/assets/img.svg';
const App = () => {
return <img src="Img" alt="import-img"/>
}
require
는 동적 import 가 가능하지만 tree shaking 을 사용하지 않아 불필요한 코드가 포함된다.
const App = () => {
return <img src={require('./src/assets/img.svg').default} alt={'require-img'} />
}
프로젝트에서는 여러개의 이미지를 동적 이미지 경로를 사용하여 가져오므로 public 디렉토리에 아이콘 svg 파일들을 이동하고, process.env.PUBLIC_URL
로 절대경로를 설정했다.
📦public
┣ 📂assets
┃ ┣ 📜cancel.svg
┃ ┣ 📜check-circle.svg
┃ ┣ 📜exclamation-triangle.svg
┃ ┣ 📜info-circle.svg
┃ ┗ 📜times-circle.svg
┗ 📜index.html
// src/notification.js
const iconMap = <{
cancel: 'cancel',
success: 'check-circle',
info: 'info-circle',
warning: 'exclamation-triangle',
error: 'times-circle',
};
const Notification = ({type}) => {
(...)
return (
<>
(...)
<img src='${process.env.PUBLIC_URL}/assets/toast-notification/${iconMap[type]}.svg'/>
<img src='${process.env.PUBLIC_URL}/assets/toast-notification/cancel.svg' />
</>
}
CRA 공식문서