게시글 렌더링
계획
- 따로 DB 구축하고 싶지 않음
- 파일 형태로만 저장 원함
- gitpage로 배포 예정
결정
- Markdown 사용
- md 파일로 게시글 작성 후 md 파일 html로 렌더링
markdown파일 불러오기
- 지정 폴더(_posts)에서 파일 가져오기
- md 파일 데이터 정리해서 객체 형태로 반환
- markdown -> HTML(String)
- html 렌더링
1. 지정 폴더의 파일 가져오기
- process.cwd() : Node.js 프로세스의 현재 작업 디렉터리를 반환
- readdirSync : 동기, 해당 디렉토리의 모든 파일 불러오기
적용 코드 : api.tsx
import fs from 'fs';
import { join } from 'path';
export function getPostIds() {
return fs.readdirSync(join(process.cwd(), '_posts'));
};
2. md 파일 정리 후 객체 변환
- md 파일을 찾기
- 찾은 파일 객체로 정리해 리턴(id, content...)
- 날짜별 정렬
적용 코드 : api.tsx
export function getPostById(id: string, fields: string[] = []) {
const realID = id.replace(/\.md$/, '');
const fullPath = join(join(process.cwd(), '_posts'), `${realID}.md`);
const fileContents = fs.readFileSync(fullPath, 'utf8');
const { data, content } = matter(fileContents);
type Items = {
[key: string]: string
};
const items: Items = {};
fields.forEach((field) => {
if (field === 'id') {
items[field] = realID;
}
if (field === 'content') {
items[field] = content;
}
if (typeof data[field] !== 'undefined') {
items[field] = data[field];
}
});
return items;
};
export function getAllPosts(fields: string[] = []) {
const ids = getPostIds(); //
const posts = ids.map((id) => getPostById(id, fields)) // 날짜별 정렬
.sort((post1, post2) => (post1.date > post2.date ? -1 : 1));
return posts;
};
3. markdown -> HTML(String)
- md 파일의 내용 html로 변환
- 비동기로 마크다운 파일 html(String)로 변환
- next : getStaticProps 이용
적용 코드 : post/[id].tsx
export async function getStaticProps({ params }: Params) {
const post = getPostById(params.id, [
'title',
'date',
'id',
'content',
]);
// 게시글의 내용 html로 변환
const content = await markdownToHtml(post.content || '');
return {
props: {
post: {
...post,
content,
},
},
};
}
적용 코드 : markdownToHtml.tsx
export default async function markdownToHtml(markdown: string) {
const result = await remark().use(html).process(markdown)
return result.toString()
}
4. html 렌더링
- dangerouslySetInnerHTML 이용
- innerHTML브라우저 DOM에서 사용하기 위한 React의 대체품.
- 일반적으로 코드에서 HTML을 설정하는 것은 실수로 사용자를 XSS(교차 사이트 스크립팅) 공격 에 노출시키기 쉽기 때문에 위험
- 따라서 React에서 직접 HTML을 설정할 수 있지만 위험하다는 것을 스스로에게 상기시키기 위해 키 dangerouslySetInnerHTML와 함께 객체를 입력하고 전달해야 함.
출처 : https://reactjs.org/docs/dom-elements.html
적용 코드
import markdownStyles from "../markdown-styles.module.css";
type Props = {
content: string,
};
const Test = ({ content }: Props) => {
return (
<div
style={{ marginTop: "30px" }}
className={markdownStyles["markdown"]}
dangerouslySetInnerHTML={{ __html: content }}
/>
);
};
export default Test;
장점
- 추가 설치 필요 없음
- 간편한 코드로 html 렌더링
단점
- XSS(cross-site scripting)에 노출될 가능성 높음.
- 따로 쿠키나 세션이 오가지 않고 사용자가 값을 입력할 일이 없어 단지 게시글을 보여주는 것이라서 사용.
렌더링 추가 방법(번외)
- html-react-parser 이용!
- npm i html-react-parser 설치
import Parser from 'html-react-parser';
type Props = {
content: string
}
const Test = ({ content }: Props) => {
return (
<div className={markdownStyle['markdown']}>
{Parser(content)}
</div>
)
};
export default Test;