OTT 공유 플랫폼 [We T] 프로젝트 (간단하게 게시판)에서 게시글에 html 태그를 이용하여 스타일을 더 하기로 하였다.
글쓰기의 에디터영역에서 글을 작성할때 html태그를 포함한 형태로 DB에 보냈다.
홈영역에서 게시글을 미리보기로 보여주고 있는데 이 부분에서 문제가 발생하였다.

<p
className="text-base line-clamp-3 text-ellipsis text-gray-700 no-style"
dangerouslySetInnerHTML={{__html: item.content}}>
</p>
오류를 확인해 보면 dangerousSlysetInnerHTML' did not match. Server: '' Client: '<></>'
서버(Server)에서 pre-rendering 된 html은 “” 이고 클라이언트(Client)는 “<> 왓챠 …</>” 이런식이니 서로 일치하지 않고 있다는 소리이다.
We T 프로젝트의 홈 ‘/’ 인덱스 페이지는 아래 그림처럼 SSG로 정적 생성된 페이지이다.
getStaticProps로 데이터를 이용한 정적 생성을 하고 있다.
export const getStaticProps = async () => {
let client = await connectDB;
const db = client.db('forum');
let data = await db.collection('post').find().toArray();
return { props: { data: JSON.parse(JSON.stringify(data)) } };
};
그렇다면 사전 렌더링을 하는 과정에서 문제가 있는 것일까?
DB에 html 태그에 클로징 태그 </> 가 빠진 것이 있는지 체크해보았다.
하지만 이 문제도 아니였다.
const ListComponent = dynamic(() => import('components/home/ListComponent'), { ssr: false })
인덱스에 있는 컴포넌트를 사전 렌더링(pre-rendering)하지 않기로 하였다.
이렇게 하니까 mis match가 일어나지 않았고 오류가 사라졌다.
하지만 이렇게 클라이언트 단에서만 존재하게 되면 SEO에 좋지 않고 초기 렌더링도 길어질 수 있다.
자 이제 남들은 대체 어떻게 했는지 찾아보자…
나는 마크업 퍼블리싱 일도 많이 했고 링크드인 HTML5테스트도 통과하여 HTML을 좀 안다고 생각하였다.
인터넷을 통해 알게 된 사실은 나는 아직도 HTML에 대해 무지 하다는 것이다.
https://www.w3.org/TR/html401/struct/text.html#h-9.3.1
W3C(W3에서) p 태그에 대해 이렇게 말하고 있다.
HTML에서 <p> 요소의 종료 태그는 생략될 수 있는데, 이는 <p> 요소가 address, article, aside, blockquote, div, dl, fieldset, footer, form, h1, h2, h3, h4, h5, h6, header, hgroup, hr, main, nav, ol, p, pre, section, table, 또는 ul 요소에 바로 이어질 경우, 혹은 부모 요소에 더 이상의 콘텐츠가 없고 그 부모 요소가 <a> 요소가 아닐 경우에 해당합니다.
<p> 요소는 문단을 나타냅니다. 이 요소는 블록 레벨 요소(자기 자신인 <p> 포함)를 포함할 수 없습니다. 저희는 작성자들이 빈 <p> 요소를 사용하는 것을 권장하지 않습니다. 사용자 에이전트는 빈<p> 요소를 무시해야 합니다.
실습으로 확인해보자.
내가 이렇게 마크업을 한다면
<p>hello<div>mr.chu</div></p>
이렇게 출력된다.
p태그 안에 div 와 같은 요소가 들어오면 p 태그는 종료 태그가 만들어진다.

결론적으로 <p> 태그는 블록 레벨 요소를 포함할 수 없고 빈<p> 태그의 사용을 권장하지 않는다.
내가 겪은 오류는 next.js의 pre-rendering 된 부분과 client 에 렌더링 된 부분이 일치 하지 않았던 것이고 이 문제의 해결법은 dangerouslySetInnerHTML을 잘못 사용했거나 SSG사용 방식이 잘못되었던게 아니라
<div
className="text-base line-clamp-3 text-ellipsis text-gray-700 no-style"
dangerouslySetInnerHTML={{__html: item.content}}
/>
마크업에 대한 이해가 부족했던 것이고 해결책은 dangerousSlysetInnerHTML를 쓸때는 div 태그 안에 쓰는게 좋을것이다.