왜 TinyMCE 라이브러리를 선택했을까?
TinyMCE 에디터는 무료 버전도 있고 가장 많이 사용되는 CKeditor보다 가벼워서 선택하게 되었습니다. 무엇보다 티스토리 블로그에서도 TinyMCE 에디터를 사용하고 있었기 때문에 참고 자료로 활용하기 좋을 것 같았죠.
📗 TinyMCE 사용 방법을 알아보자
1. tinymce 설치
npm i tinymce @tinymce/tinymce-react
2. TinyMceEditor 컴포넌트 작성
// src/components/editor/tinymce/TinymceEditor.tsx
import React, { useRef } from 'react';
import { Editor } from '@tinymce/tinymce-react';
const TinyMceEditor = () => {
const editorRef = useRef(null);
const tinymcePlugins = ['link', 'lists', 'autoresize'];
const tinymceToolbar =
'blocks fontfamily |' +
'bold italic underline strikethrough forecolor backcolor |' +
'alignleft aligncenter alignright alignjustify |' +
'bullist numlist blockquote link';
return (
<Editor
onInit={(e, editor) => (editorRef.current = editor)}
init={{
plugins: tinymcePlugins,
toolbar: tinymceToolbar,
min_height: 500,
menubar: false,
branding: false,
statusbar: false,
block_formats: '제목1=h2;제목2=h3;제목3=h4;본문=p;'
}}
/>
);
};
export default TinymceEditor;
이제 Next.js 프로젝트에서 TinyMCE 에디터를 사용할 수 있습니다!
3. 이미지 업로드 기능 구현 (+ 보너스)
TinyMCE 에디터에 이미지를 업로드하는 기능도 추가해 보았습니다.
src/components/editor/tinymce/TinymceEditor.tsx
import React, { FC, useRef } from 'react';
import { useRecoilState } from 'recoil';
import { tinymceEditorState } from '../../../recoil/tinymce';
const TinyMceEditor:FC<TinymceEditorProps> = ({ onOpenFile }) => {
const editorRef = useRef(null);
const [tinymceEditor, setTinymceEditor] = useRecoilState(tinymceEditorState);
return (
<Editor
onInit={(e, editor) => (editorRef.current = editor)}
init={{
plugins: tinymcePlugins,
toolbar: tinymceToolbar,
// 생략..
setup(editor) {
setTinymceEditor(editor); // activeEditor를 전역적으로 관리
// 이미지를 업로드하는 버튼 추가
editor.ui.registry.addButton('image-upload', {
icon: 'image',
tooltip: '업로드',
onAction: () => {
editor.execCommand('image-upload');
},
});
editor.addCommand('image-upload', onOpenFile); // onOpenFile 함수 실행
},
}}
/>
);
};
export default TinymceEditor;
우선 위와 같이 코드를 수정해주세요.
에디터가 실행될 때 가장 처음 실행되는 함수인 setup 함수에서 setTinymceEditor(editor)를 수행하여 tinymceEditorState 값에 activeEditor 값을 저장했습니다.
(Recoil의 개념을 모르시는 분들은 이 글을 참고하고 오셔도 좋을 것 같아요. 😊)
그런데 여기서 잠깐, 저의 우여곡절을 함께 볼까요?
(해결 방안만 보고 싶다면 🖼 Recoil을 사용하여 이미지 업로드하기부터 보시면 됩니다.)
에러 발생 코드
import tinymce from 'tinymce';
tinymce.ActiveEditor.execCommand('mceInsertContent', 'false', '<img src="' + imageSrc + '" />');
만약 이미지를 업로드하려고 할 때 tinymce 라이브러리를 import하고 위 코드처럼 코드를 작성한다면 (이미지를 에디터에 보이게 하기 위해), 처음에는 문제없이 잘 수행될 것입니다. 그러나 에디터 페이지를 새로고침을 한다면 navigator is not defined 에러와 함께 에디터 페이지를 더 이상 볼 수 없습니다. 왜 그럴까요?
Next.js에서 새로고침은 SSR 방식으로 동작하기 때문에 서버에서 tinymce 모듈을 불러오려고 할테지만 tinymce는 SSR을 지원하지 않기 때문에 navigator가 존재하지 않는 서버에서는 tinymce 모듈을 찾을 수가 없는 거죠. 이 문제를 해결하기 위해 떠올린 방법은 Recoil 라이브러리를 사용해서 tinymce.activeEditor 값을 전역으로 관리하자! 였습니다.
🖼 Recoil을 사용하여 이미지 업로드하기
(1) recoil 설치
npm i recoil
recoil을 사용하기 위해서는, 먼저 recoil을 프로젝트에 설치해야 합니다.
(2) atom 생성
src/recoil/tinymce.ts
import { atom } from 'recoil';
import { Editor } from 'tinymce';
export const tinymceEditorState = atom<Editor>({
key: 'tinymceEditor',
default: null,
dangerouslyAllowMutability: true,
});
tinymce.activeEditor 객체를 저장할 tinymceEditorState atom을 생성합니다.
(3) 이미지 업로드 실행
src/components/editor/Editor.tsx
import { useRecoilValue } from 'recoil';
import { tinymceEditorState } from '../../recoil/tinymce';
const Editor = () => {
const tinymceEditor = useRecoilValue(tinymceEditorState); // atom state
const handleUploadImage = async (imageUrl: string, filename: string) => {
const dom = tinymceEditor.dom;
tinymceEditor.execCommand(
'mceInsertContent',
false,
'<img src="' + imageUrl + '" data-filename="' + filename + '" />'
);
};
...more code
};
TinymceEditor 컴포넌트에서 tinymceEditorState atom을 업데이트했기 때문에 해당 atom을 구독하고 있던 Editor 컴포넌트가 업데이트된 state로 re-render되고, tinymceEditorState에 저장된 activeEditor 객체를 참조하여 명령어를 수행할 수 있습니다!
'Next.js' 카테고리의 다른 글
SSR VS CSR 차이점 (1) | 2024.01.21 |
---|---|
[Next.js] SSR 페이지에서 session pre-fetch 하기 (next-auth) (2) | 2023.05.01 |
[Next.js] Planet Scale + Prisma 배포하기2 (Vercel) (0) | 2023.04.04 |
[Next.js] Planet Scale + Prisma 배포하기 (Vercel) (0) | 2022.10.18 |