블로그처럼 글을 쓸 때 H1, H2, bold, italic, link 등의 요소들을 어떻게 처리할 지, 이를 어떻게 DB에 저장해서 html로 렌더링해 줄 지 고민을 하다가 Quill Editor를 적용해 보기로 하였다.
npm i react-quill
react-quill을 설치하고 컴포넌트에 Quill을 Import할 경우 document is not defined
라는 에러가 발생한다. SSR 환경에서는 브라우저의 document에 접근할 수 없기 때문이다. 이를 해결해 주기 위해 dynamic import를 사용했다.
import dynamic from "next/dynamic";
export const ReactQuill = dynamic(() => import("react-quill"), {
ssr: false,
loading: () => (
<div>
남기고 싶은 기록을 자유롭게 적어주세요.
</div>
),
});
이렇게 하면 SSR을 끝내고, document가 그려진 후에 import가 되기 때문에 정상적으로 사용 가능하게 된다.
<ReactQuill
theme="bubble"
placeholder="남기고 싶은 기록을 자유롭게 적어주세요."
modules={modules}
value={value}
onChange={setValue}
/>
Quill Editor에는 2가지의 테마가 있는데, snow와 bubble이다.
snow는 벨로그처럼 toolbar가 상단에 고정된 형태이고, bubble은 작성한 글을 드래그하면 toolbar가 나타나는 형태이다.
우리는 최대한 깔끔하게 디자인하고 싶어서 bubble을 사용하기로 했다.
bubble 테마 커스텀하기
1. root에 quill.custom.css 파일 만들기
// quill.custom.css ... .ql-container { box-sizing: border-box; font-family: "Gowun Batang"; font-size: 1.25rem; font-weight: 400; color: #565759; min-height: 40rem; margin: 0px; position: relative; } @media screen and (max-width: 600px) { .ql-container { font-size: 1.125rem; } } ...
2.
_app.tsx
에 import하여 적용하기// _app.tsx import "/quill.custom.css";
modules
에는 toolbar로 지정하고싶은 요소들을 적어주면 된다. 우리는 글에 집중하기 위해 정말 간단한 요소들만 넣어줄었다!
export const modules = {
toolbar: [
[{ header: [1, 2, false] }],
["bold", "italic", "underline", "strike", "blockquote"],
[
{ list: "ordered" },
{ list: "bullet" },
{ indent: "-1" },
{ indent: "+1" },
],
["link"],
],
};
modules
은 toolbar에 나타나는 요소들을 적어준 거라면, formats
은 ReactQuill에서 사용할 수 있는 기능들을 제한해주는 기능을 한다.
export const formats = [
"header",
"bold",
"italic",
"underline",
"strike",
"blockquote",
"list",
"indent",
"link",
];
modules
만 적어준 경우,modules
에 color 요소를 넣지 않았음에도 초록색 글씨를 복사-붙여넣기 한 경우, 초록색 글씨가 적히게 된다! 이를 막고, 우리가 toolbar에 지정한 기능들만 사용하게 하려면formats
을 적어주어야 한다.only modules
use formats
테스트를 해본 결과 html 태그가 붙으면서 value를 기록할 수 있게 되었다.
이제 태그가 붙은 글을 그대로 mysql에 저장을 해주고, 이를 html로 렌더링해주는 일만 남았다!
post column의 타입을 text로 지정했는데, 원래는 varchar(65535)로 만들었었다.
하지만 긴 글을 샘플로 넣어보니 데이터가 너무 길어서 저장이 되지 않았다..
이를 해결하기 위해 text로 바꿔주었는데, 왜 varchar는 안된 것인지 정확히 알아내지 못했다..
아직 detail 페이지나 edit 페이지를 작업하기 전이라 간단한 테스트만 해 보았는데,
DB로부터 받은 data를 html태그가 붙은채로 렌더링 시키면 당연히 태그까지 같이 나와버렸다.
방법을 찾아보니 dangerouslySetInnerHTML을 알게 되었다. React에서는 직접 HTML을 설정할 수는 있지만, 위험하다는 것(XSS 공격에 쉽게 노출)을 상기시키기 위해 dangerouslySetInnerHTML을 작성하고 아래처럼 __html 키로 객체를 전달해야 한다.
<div dangerouslySetInnerHTML={{__html: post}}></div>
공부하다보니 dangerouslySetInnerHTML은 가능하면 사용하지 않는 것이 좋지만 꼭 사용해야 할 경우 XSS 공격을 방지할 수 있도록 dompurify와 같은 sanitization library와 함께 사용해주는 것이 좋다는 것을 알게되었다. detail 페이지나 edit 페이지를 작업할 때
dompurify
를 install 하여 적용해 보아야 겠다.