코로 넘어져도 헤딩만 하면 그만
[MD]Next의 SSR과 Quill의 module 충돌 문제 (500 Server Error) 본문
언젠가부터 콘솔에 도통 알 수 없는 에러 하나가 괴롭히고 있었으니......
500번대 에러가 갑자기 뜰 게 뭐란 말인가?
심지어 개발 환경에서 구현할 때에는 문제 없어서 흐린 눈 하고 끝까지 미룬 에러였다.
일단 다행이긴 한데... 다른 기능들이 완성되고 나니, 이대로 에러를 안고 메인에 합치기도 양심이 찔리는지라.
최종 에러 수거에 나섰다.
초반에 어디서 문제가 생기는지 알 수가 없어서 한창 삽질을 했다. 결국 페이지를 기본 세팅으로 돌려두고 기능을 하나씩 추가하는 과정에서 문제가 maxLengthModule(저번에 직접 세팅해서 넣은 quill 최대 글자수 제한하는 모듈)에서 발생하는 것을 알게 되었다.
Next는 React와 달리 SSR로 동작하는 구간이 있는데 여기서 뭔가..... 문제가 생겨서 500이 발생했을 확률이 높아 보였다. 따라서 maxLengthModule()이 서버 환경에서도 실행되어 Next.js SSR과 충돌이 발생했을 가능성을 최우선으로 두고 해결하기로 했다.
1) 최상단에서 import 하던 maxLengthModule를 useEffect 내부로 옮기고 내부에서 모듈 등록
useEffect 내부에서만 불러올 때 비동기로 await를 써서 불러온다. (이렇게 하지 않고 최상위에서 불러올 경우 똑같이 서버 에러를 뱉는 것을 확인. useEffect에서 실행하면 모듈 등록을 클라이언트 렌더링 이후에만 하도록 처리할 수 있다.
maxLengthModule(); //기존까지 코드 상단 useEffect 외부에서 실행함
const MAX_TEXT_LENGTH = 1500;
useEffect(() => { //내부로 수정
const initializeModules = async () => {
const { maxLengthModule } = await import(
'@/configs/tanstack-query/quill/maxlengthModule'
);
if (typeof window !== 'undefined') {
maxLengthModule();
}
setModules({
toolbar: {
container: '#toolbar',
handlers: {
createVote: handleAddVote,
addHashTag: handleAddHashTag
}
},
maxlength: { maxLength: MAX_TEXT_LENGTH }
});
};
initializeModules();
}, [handleAddVote]);
2) 서버 사이드 렌더링 방지하기 (typeof window !== 'undefined')
모듈 maxLengthModule를 구현한 코드 내부에 클라이언트에서만 실행되게 조건을 따로 주었다.
이것저것 지우면서 봤는데 모듈을 등록하기 위해 쓰는 Quill.register 부분이 문제가 되는 것 같다. 와중에 모듈 중복 등록 문제도 있는 것 같아서 조건에 포함시켰다.
import { Quill } from 'react-quill-new';
interface MaxLengthOptions {
maxLength: number;
}
export const maxLengthModule = () => {
if (typeof window !== 'undefined' && !Quill.imports['modules/maxlength']) {
//이 부분
Quill.register(
'modules/maxlength',
function (quill: InstanceType<typeof Quill>, options: MaxLengthOptions) {
quill.on('text-change', () => {
const currentText = quill.getText().trim();
if (currentText.length > options.maxLength) {
quill.deleteText(options.maxLength, currentText.length);
}
});
}
);
}
};
📍코드 비교: useEffect를 사용하던 전과 후이다.
글자수 모듈 관련 코드를 useEffect 내부로 옮겨온 것을 볼 수 있다.
단일 useEffect 내부에서 전부 처리하는 게 효율적인지에 관해서는 좀더 생각을 해봐야겠지만......
useEffect(() => {
setModules({
toolbar: {
container: '#toolbar',
handlers: {
createVote: handleAddVote,
addHashTag: handleAddHashTag
}
},
maxlength: { maxLength: MAX_TEXT_LENGTH }
});
}, [handleAddVote]);
useEffect(() => { //내부로 수정
const initializeModules = async () => {
const { maxLengthModule } = await import(
'@/configs/tanstack-query/quill/maxlengthModule'
);
if (typeof window !== 'undefined') {
maxLengthModule();
}
setModules({
toolbar: {
container: '#toolbar',
handlers: {
createVote: handleAddVote,
addHashTag: handleAddHashTag
}
},
maxlength: { maxLength: MAX_TEXT_LENGTH }
});
};
initializeModules();
}, [handleAddVote]);
같은 코드지만 아래처럼 두 개의 useEffect로 그냥 분리도 가능할 것 같고.....
중복 렌더링 방지가 나은지 가독성이 나은지... 가독성이 낫겠지...
useEffect(() => {
const registerMaxLengthModule = async () => {
const { maxLengthModule } = await import('@/configs/tanstack-query/quill/maxlengthModule');
if (typeof window !== 'undefined') {
maxLengthModule();
}
};
registerMaxLengthModule();
}, []); // 초기 한번
useEffect(() => {
setModules({
toolbar: {
container: '#toolbar',
handlers: {
createVote: handleAddVote,
addHashTag: handleAddHashTag
}
},
maxlength: { maxLength: MAX_TEXT_LENGTH }
});
}, [handleAddVote]);
💡결론: Next와 다른 브라우저 라이브러리를 쓸 때 SSR과 충돌 나서 500 에러가 발생 가능하다.
- 브라우저 전용 라이브러리(Quill) 사용할 때 항상 typeof window !== 'undefined' 가 필요한지 체크하기. 서버 사이드 렌더링(SSR) 환경과 충돌하지 않도록 클라이언트 전용 코드로 처리하면 된다.
- 모듈 등록은 useEffect 내부에서만 처리한다. 즉클라이언트 렌더링 이후에만 모듈을 초기화하도록 충돌을 방지하는 게 좋겠다.
+) 하루 지나고 생각해보니 quill 처음 적용할 때도 이와 비슷한 문제가 발생해서 dynamic으로 적용했던 것 같다.
Next.js와 같은 서버 사이드 렌더링(SSR) 환경에서 발생할 수 있는 문제. quill editor은 기본적으로 document 객체를 조작하여 동작한다. 그런데 이 document 객체는 브라우저 환경에서만 사용할 수 있지만, Next의 경우 SSR로 작동한다. 따라서 서버에서는 브라우저 관련 객체들이 정의되어 있지 않아 이러한 객체를 참조하면 에러가 발생한다.
'Project' 카테고리의 다른 글
[MD]Next에서 Quill의 CSS FOUC 깜빡임 문제 (0) | 2025.02.07 |
---|---|
React 리펙토링 중에 만난 defaultProps 경고 에러 (1) | 2025.02.05 |
[MD]ReactQuill register로 텍스트 글자 수 제한(MaxLength) (0) | 2025.01.08 |
[MD]Quill의 placeholder에서 한글 IME 인식 문제: 기능 구현 (0) | 2025.01.07 |
[MD]React-quill의 칼라 팔레트 개수 변경 (0) | 2024.12.30 |