커뮤니티 프로젝트를 개발하기 위해선 이미지 기능은 필수불가결하다.
이미지 기능에서 가장 중요한 부분은 어떻게 저장하느냐 이다.
Problem
처음 진행했던 프로젝트인 Travel Maker에선
협업을 하다보니 내 컴퓨터에서 올린 이미지가 다른 팀원의 컴퓨터에선 보이지 않기 때문에(로컬에 저장) 작업하기가 불편했다.
이러한 점 때문에 이미지를 Base64코드로 변환해서 DB에 저장하는 지금 생각하면 아주 무식한 방법으로 개발을 진행했다.
기능은 문제없이 잘 작동했다. DB에 있는 코드를 불러와서 이미지화 하면 되기때문에 협업도 잘 되었다.
하지만 문제는 이미지를 Base64코드로 변환하면 크기가 4배 ~ 10배정도 커진다는 문제 때문에 글을 쓸때 이미지를 4~5개 업로드하면 DB 컬럼의 데이터 타입을 Long, Blob으로 했음에도 불구하고 너무 길어져서 들어가지 않는 문제가 생긴다.
뿐만아니라 OracleDB 워크벤치로 테이블을 확인하면 워크벤치가 멈춰버리는 문제까지 발생했다.
Solve
해결방법은 많지만 내가 선택했던 방식은 이미지 호스팅 서비스를 따로 이용하는 방법이다.
이미지 호스팅 서비스는 S3, Firebase등 많은 서비스들이 있지만 Firebase를 선택했다.
구조는 간단하다.
이미지를 업로드하면 해당 이미지를 Firebase 서버에 저장한다
DB에는 해당 Firebase에 저장된 이미지의 주소를 저장는 구조
공식문서도 잘 되어있고 무엇보다 한글을 지원하기 때문에 따라하기 어렵지 않고 무료인게 가장 큰 장점이다.
하지만 사용하다보니 로컬에 저장된 이미지를 불러오는것보다 속도가 많이 느리다는 단점이 있다.







React 예시
1. src/Firebase.js
// Import the functions you need from the SDKs you need
import { initializeApp } from "firebase/app";
import { getAnalytics } from "firebase/analytics";
import { getStorage } from "firebase/storage";
// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries
// Your web app's Firebase configuration
// For Firebase JS SDK v7.20.0 and later, measurementId is optional
const firebaseConfig = {
apiKey: ,
authDomain:,
projectId: ,
storageBucket: ,
messagingSenderId: ,
appId: ,
measurementId:
};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
export const storage = getStorage();
const analytics = getAnalytics(app);
react quill 예시
import React, { useContext, useEffect, useMemo, useRef, useState } from "react";
import ReactQuill, { Quill } from "react-quill";
import ImageResize from "quill-image-resize";
import "react-quill/dist/quill.snow.css";
import axios from "axios";
import QuillImageDropAndPaste from "quill-image-drop-and-paste";
import "../css/Quill.css";
import { storage } from "../Firebase";
import { uploadBytes, getDownloadURL, ref } from "firebase/storage";
Quill.register("modules/ImageResize", ImageResize);
Quill.register("modules/imageDropAndPaste", QuillImageDropAndPaste);
const QuillTest = ({ update }) => {
const [imageUrl, setImageUrl] = useState(""); // 새로운 상태 추가
// 배포용 URL
const quillRef = useRef(null); // useRef로 ref 생성
// 이미지 핸들러
const imageHandler = () => {
const input = document.createElement("input");
input.setAttribute("type", "file");
input.setAttribute("accept", "image/*");
input.click();
input.addEventListener("change", async () => {
const editor = quillRef.current.getEditor();
const file = input.files[0];
const range = editor.getSelection(true);
try {
// 파일명을 "image/Date.now()"로 저장
const storageRef = ref(
storage,
`image/${Date.now()}`
);
// Firebase Method : uploadBytes, getDownloadURL
await uploadBytes(storageRef, file).then((snapshot) => {
getDownloadURL(snapshot.ref).then((url) => {
// 이미지 URL 에디터에 삽입
editor.insertEmbed(range.index, "image", url);
// URL 삽입 후 커서를 이미지 뒷 칸으로 이동
editor.setSelection(range.index + 1);
console.log('url 확인', url);
setImageUrl(url);
});
});
} catch (error) {
console.log(error);
}
});
};
const modules = useMemo(() => {
return {
toolbar: {
container: [
["image", "link", "video"],
[{ header: [1, 2, 3, false] }],
["bold", "italic", "underline", "strike", "blockquote"],
[{ 'color': [] }, { 'background': [] }],
],
handlers: {
image: imageHandler,
},
},
imageDropAndPaste: {
handler: imageDropHandler,
},
ImageResize: {
parchment: Quill.import("parchment"),
mopdules: ["Resize", "DisplaySize"],
},
};
}, []);
const formats = [
"header",
"bold",
"italic",
"underline",
"strike",
"blockquote",
"image",
];
const [value, setValue] = useState('');
return (
<div>
<ReactQuill style={{ height: "600px" }}
ref={quillRef} // useRef로 생성한 ref를 연결
theme="snow"
placeholder="내용을 입력해주세요."
value={value}
onChange={setValue}
modules={modules}
/* formats={formats} */
/>
</div>
);
};
export default QuillTest;
'프로젝트 코드리뷰 > ITTY' 카테고리의 다른 글
익명 댓글 인덱싱 (0) | 2024.02.13 |
---|---|
게시글 조회 (+댓글 +대댓글) 랜더링 속도 개선 (0) | 2024.02.13 |
리액트 스와이프(드래그 스크롤) 기능구현 (0) | 2023.11.21 |