리액트 이미지 렌더링 방식

v1.0.0
@young_log|React|Published on 2026-01-13

"왜 내 리액트 프로젝트엔 Xbox가 아니라 엑박이 뜰까? ㅋㅋ"

@
Details
Changelog
Dependencies

웹사이트를 사용하다보면 간혹 아래 이미지를 쉽게 볼 수 있다. 이걸 엑박이 뜬다라고 한다.
엑박? 아 MS가 만든 게임기 Xbox? 아님^^
엑스(X)가 그려진 박스(Box)의 준말이다

image.png

엑박(Broken Image)은 이미지 경로를 찾지 못해 브라우저에 이미지 깨짐 아이콘이 나타나는 현상이다.

  1. 경로 오타(대소문자 주의 필요)

  2. Public vs Src 경로 혼동

    Public: /image.png 로 바로 접근 가능하다.

    Src: 반드시 상단에서 import 하거나 require 써야 한다.

    src 폴더에 있는 이미지는 public 폴더와는 달리, 빌드 도구(Vite나 Webpack)가 이미지를 직접 관리하기 때문에 반드시 파일 자체를 불러오는 과정이 필요!!

그럼 이제 이를 기반으로 이미지가 렌더링 되는 4가지 방법을 알아보자.


External URL (외부 링크 방식)

가장 간단한 방식이며, 구글이나 네이버 같은 서버에 이미 있는 주소를 그대로 가져오는 것이다.

<img src="https://google.com/logo.png" />

외부에 있는 자원을 가져와 사용하는 것이어서 우리 프로젝트 용량을 차지하지 않는다.
하지만 원본 주소에서 이미지를 삭제하면 우리 사이트에서도 엑박(이미지 깨짐)이 뜰 우려가 있다.

이 방식은 주로 뉴스 기사 썸네일, 사용자 프로필 이미지 등 외부 API 데이터를 보여줄 때 사용된다.


Public 폴더 방식 (정적 자원)

프로젝트의 public 폴더에 이미지를 넣어두고 쓰는 방식이다.

<img src="/images/logo.png" />

빌드(배포) 프로세스에 포함되지 않고 그대로 복사된다. 파일 이름이 바뀌지 않아서 관리하기 쉽지만, 브라우저 캐싱 최적화가 조금 어려울 수 있다.
왜냐? public 폴더는 React가 관여하지 않기 때문에, 빌드 후에도 이름도 그대로 유지된다.
브라우저 캐싱 최적화가 안되어 있어서 이미지가 잘 안 바뀜.

그대로 복사된다는 말은 리액트의 경우 빌드 파일 Dist 폴더를 배포하는데, 여기에 그대로 가공 없이 통째로 놓인다는 말임.

파비콘(Favicon), 로딩 바 이미지처럼 절대 변하지 않는 고정 자원에 많이 쓰인다.


Assets/Src 폴더 방식 (Import 방식) 🌟 (추천)

src/assets 폴더에 넣고 import로 가져오는 방식이다.

JavaScript 기준 코드 예시

import logo from './assets/logo.png'; <img src={logo} />

비트(Vite)나 웹팩이 빌드할 때 이미지를 최적화. (용량을 줄이거나 파일명 뒤에 고유 번호를 붙여서 캐시 문제를 해결해줘요.)
Vite/Webpack이 관리하기 때문에, src 폴더에 넣고 import 하면 빌드할 때 리액트는 이 파일의 이름을 암호처럼 바꿔버린다.

원본: logo.png

빌드 후: logo.a1b2c3d4.png (이름 뒤에 랜덤한 문자가 붙음)

내용을 수정했을 때 이름이 같으면 브라우저가 "어? 예전에 본 건데?" 하고 옛날 이미지를 보여주기 때문이다.
이름을 강제로 바꿔서 새 이미지임을 인식 → 캐싱 최적화가 잘 되어 있다는 것

서비스 로고, 아이콘, 배경 이미지 등 내 서비스 전용 그래픽들에 사용된다.


File Upload & Blob 방식 (사용자 업로드)

이건 사용자가 자기 컴퓨터에 있는 파일을 선택한 후, 서버에 전송되기 전에 내 화면에서 미리 볼 수 있게 한다.
즉, 서버를 거치지 않고 브라우저 메모리를 사용해서 잠시 파일을 띄우는 것임.

위의 방식들을 보면 각각 이미지들은 경로와 주소를 가지고 있는데 이 방식은 당연히 업로드 전에는 없겠죠?
이때 이 함수를 쓰면 브라우저는 임시 주소를 하나 만들어 준다.

<img> 태그에 쓰면 보일거고, 브라우저를 끄면 사라질거야”

URL.createObjectURL(file)

생성된 주소 예시(우리 브라우저의 메모리 주소): blob:http://localhost:3000/a1b2-c3d4...

서버에 올라가기 전, 브라우저 메모리에 있는 임시 주소를 만들어 화면에 즉시 렌더링해준다.
이미지 업로드 후 "미리보기" 보여줄 때 이걸 쓴다.
그래서 우리는 빠른 UX 경험할 수 있다. (서버를 거치지 않으니 매우 빠름)

그런데 아무래도 임시주소를 생성하려먼 브라우저 메모리에 부하가 있을 수 있다.
URL.revokeObjectURL(file) 를 호출해서 메모리 누수 방지하기!


결론

| 방식 | 경로 | 최적화 여부 | 주요 용도 |
| -------------- | ------------- | ------------- | ------------------ |
| URL | https://... | ❌ (원격지 의존) | 외부 API 데이터, 프로필 사진 |
| Public | /logo.png | ❌ (복사만 됨) | 정적 리소스 (favicon 등) |
| Src/Import | import ... | ✅ (Vite가 최적화) | 서비스 내부 로고, 아이콘 |
| Blob/File | blob:... | ❌ (임시 주소) | 업로드 미리보기 |

Comments_Log
TERMINAL
DEBUG CONSOLE
OUTPUT
~/stay-young-loggit(main)npm run comment:write
nickname:
content:
-- TOTAL COMMENTS: 0 --
[LOADING...] fetching data from supabase...