블로그 구조에 관해서
블로그에 게시글 하나 쓸 의욕이 생길 때까지 15일이나 걸리다니 믿을 수가 없다. 아무튼, 전에 말했던 대로, 블로그 구조에 관해서 게시글을 한번 써보기로 했다. 좀 엉망이긴 하지만.
개요
kkiro.kr에서는 React의 서버 사이드 렌더링 (SSR)을 사용한 정적 블로그 생성기를 사용한다. JSON 조각들을 사용해서 클라이언트에서 필요한 정보만 새로 다운로드 받을 수도 있게 했다.
블로그 생성기는 크게 쪼개면 다음과 같이 나누어진다.
- 클라이언트 코드 - 클라이언트 코드다. redux와 react-router를 사용해서 주소에 맞는 페이지를 띄우고 메타데이터를 관리하게 했다. Webpack을 사용해서 JS 번들을 생성한다.
- 메타데이터 생성기 -
posts
폴더를 돌아다니면서 게시글 메타데이터를 생성한다. - 메타데이터 분할기 - 메타데이터를 읽어 JSON 파일 여러개로 조각낸다.
- React 렌더러 - 클라이언트 코드를 읽어 매 페이지를
renderToStaticMarkup
을 통해 HTML 파일을 생성한다. - RSS / Atom 렌더러 - pug 템플릿 엔진을 사용해서 RSS / Atom 피드를 생성한다.
만든 이유
당연히 이미 나와있는 정적 블로그 생성기를 사용해서 블로그를 만들 수도 있었다. 근데 별로 쓰기 싫었다. 가능하면 React같은걸 사용해서 단일 페이지 어플리케이션 (SPA)을 만들어 보고 싶었기 때문이다. 응답 속도같은게 더 빠르기도 하고.
아니면 상용화된 블로그 플랫폼을 쓸 수도 있었다 - 티스토리나 Blogger, Tumbler같은 것들. 근데 별로 수정하기가 용이하지가 않아서 쓰기 싫었다. 다국어 지원같은걸 구현할 수 있을까?
그래서 React를 사용하는 정적 블로그 생성기를 찾아봤는데, Gatsby라고 딱 하나 있었다. 근데 이건 전체 게시글 정보 (내용까지 포함해서)를 JS파일 하나로 묶어버린다. 초반엔 괜찮겠지만 나중에 블로그가 커질수록 개판이 되어버리는 구조다. 새 버전에서는 GraphQL을 사용해서 메타데이터와 매 페이지마다 알맞는 JSON을 생성하는 구조를 쓴다고 한다. 되게 괜찮아보이긴 하는데, 아직 개발중이다. 1.0 버전이 나오면 한번 쓰는걸 고려해야겠다.
어쨌든, GraphQL이 괜찮아 보이긴 하는데 아직은 별로 쓸 준비가 안된 것 같다. 아직 성숙하지 않은 것 같기도 하고.
클라이언트 코드
클라이언트 코드는 그냥 클라이언트 코드다. Redux를 사용해서 사이트 상태를 관리하도록 했는데, 사실 별로 Redux를 쓸 필요는 없긴 했다.
Redux를 통해 구현한 메타데이터 로딩 미들웨어는 메타데이터를 불러와서 알아서 메타데이터 분할기의 스키마 파일을 통해 데이터를 맞는 위치에 넣어준다.
서버 사이드 렌더링을 하기 위해서 react-router의 구조를 활용하는 prefetch 메커니즘을 사용하기도 했다. 블로그 생성기가 webpack을 호출해 클라이언트 코드를 번들하기도 한다.
사실 특별히 설명할 내용은 없긴 하다. 그냥 React, react-router, redux를 사용한 평범한 웹사이트다.
메타데이터 생성기
메타데이터 생성기는 posts
폴더를 돌아다니면서 게시글 메타데이터를 생성한다.
이를 위해 GraphQL같은걸 쓰는게 아니라서, 메타데이터를 생성할 때 실제 게시글 데이터를
페이지 렌더링할 때 필요한 게시글 리스트, 태그 리스트 등등으로 나눠버린다.
한국어를 지원하기 위해서 다국어 지원도 추가했다. 이를 위해서 각 리스트와 게시글 데이터는 언어로 나누어져 있다.
메타데이터 생성기는 이 커다란 JSON 메타데이터를 생성한다. 이는 사이트 나머지 파일들을 빌드할 때 사용된다.
{
site: {
/* Site configuration */
},
tags: {
ko: ['test', 'tag'],
en: ['test', 'tag']
},
posts: {
ko: [
{ /* Post */ },
{ /* Post */ }
]
},
postEntries: {
ko: {
postName: { /* Post with content */ }
}
},
tagEntries: {
ko: {
test: [
{ /* Post */ },
{ /* Post */ }
]
}
}
}
별로 안 이뻐보이긴 하는데 매 페이지마다 JSON 파일을 쪼개기 위해서 이런 구조를 취했다. 생성이 끝나고 나면, 데이터가 분할기로 넘어간다.
메타데이터 분할기
메타데이터 분할기는 메타데이터 파일을 조각내서 동적으로 생성된 키와 함께 dictionary에 넣어버린다. 이 키 값은 파일 이름이 된다.
그리고 이 파일들은 클라이언트가 읽을 수 있도록 메타데이터 폴더에 넣어진다.
이는 메타데이터가 어떻게 분할될지 정의해 놓은 스키마 파일을 통해 처리된다.
이 단계에서부터는 사이트가 클라이언트 코드에 의해 렌더링될 수 있게 되고, 실제로 이 메타데이터 파일들은 실시간 프리뷰를 위해 개발 서버에서 사용된다.
아무튼, 아직 페이지 렌더링과 Atom / RSS 피드 렌더링이 남아있다.
React 렌더러
React 렌더러는 클라이언트 코드를 사용해서 웹사이트의 HTML 파일을 구워낸다.
일단, '링크 렌더러'가 메타데이터를 읽어서 렌더링해야 할 URL들을 생성해낸다. React 렌더러는 그 URL들을 받아서 실제로 HTML 파일들을 생성해낸다.
사실 특별히 설명할 내용은 별로 없고, React의 renderToStaticMarkup
으로 HTML을 생성한다.
근데 webpack이랑 webpack-isomorphic-tools을 생성기 안에서 붙이느라 조금 고생하긴 했다.
redux 상태 안에 필요한 데이터를 받아오기 위해서 prefetch를 사용한다. 이 redux 상태는
HTML 파일에 같이 들어간다. 처음에는 JSON.stringify
를 사용했는데, 이러면 나중에
XSS 문제를 겪을 수도 있다! (HTML은 이스케이프 처리가 안된다) 그래서
serialize-javascript
를 사용해서 HTML에 구워내고 있다.
react-helmet을 사용해서 헤더 태그들을 구워내기도 한다.
페이지를 다 구워내고 나면, 그냥 HTML 파일로 저장해버리고 끝난다.
RSS / Atom 렌더러
RSS / Atom 렌더러는 블로그의 RSS / Atom 피드를 생성한다. 사실 안 만들어도 되는데 RSS 리더기를 자주 사용하는 편이라서 나도 블로그에 RSS / Atom 피드를 구현하고 싶었다.
처음에는 HTML같이 React를 사용해서 XML을 생성하고 싶었는데, 별로 의미가 없을 뿐더러 React는 XML 생성을 지원하지도 않는다. 그래서 그냥 pug 템플릿 엔진을 써서 구워내기로 했다.
pug 템플릿은 메타데이터를 받아다가 XML 파일을 구워낸다. 끝! 별거 없다. 마크다운 문서가 아니라 HTML을 내장해야하는 관계로 마크다운 렌더러를 같이 끼워넣어야 하긴 했다.
다국어 지원을 추가하고 나서는 RSS / Atom 피드도 매 언어마다 XML을 따로 생성해야 했다. 그래서 선택된 언어로 된 게시글만 렌더링하도록 수정했다.
마치며
블로그 구조에 대해서는 이게 거의 전부다. 정적 생성을 한번도 해본 적이 없어서 만드는게 은근히 재밌었기도 하다. 사실 별거 없고 일반 웹 서버처럼 서버 사이드 렌더링을 사용하면 되는거였다.
코드가 좀 더럽긴 하지만, https://github.com/yoo2001818/kkiro.kr 에서 소스코드를 둘러볼 수 있다.