이번엔 다른 저장소 얘기가 아니라, 이 사이트(Frontend Note) 자체의 빌드 과정이에요. 맞는지 궁금하시면 이 페이지에서 "페이지 소스 보기"를 열어 <title>이랑 JSON-LD 스크립트가 실제로 들어있는지 직접 확인해보셔도 돼요.

React Helmet Async는 클라이언트 렌더링 이후에만 동작해요

이 사이트는 React Router 기반 SPA고, 페이지마다 react-helmet-async<Helmet>으로 title이랑 meta description을 선언해요. 문제는 이 값이 브라우저에서 자바스크립트가 실행된 다음에야 <head>에 반영된다는 거예요. 크롤러가 자바스크립트를 안 돌리거나 포기해버리면, index.html의 기본 <title>만 보게 될 위험이 있더라고요.

그래서 빌드 후 단계에서 정적 HTML에 값을 주입했어요

npm run build 다음에 postbuild 스크립트로 scripts/prerender.js가 돌아요. 이 스크립트는 src/data/posts.js의 글 목록을 그대로 읽어서, 글마다 dist/post/:slug/index.html을 만들고 그 안의 <title>, meta description, JSON-LD를 각 글의 진짜 데이터로 바꿔 써요. React가 브라우저에서 다시 렌더링하는 거랑은 별개로, 빌드 시점에 이미 완성된 정적 HTML 파일이 각 글 경로에 준비돼있는 거예요.

정적 파일이랑 SPA 번들이 같은 값을 두 번 표현해요

이 구조의 특징은 같은 메타 정보가 두 번 존재한다는 거예요. 정적 index.html 안의 값은 빌드 시점 데이터의 스냅샷이고, 브라우저에서 React가 마운트된 다음엔 Helmet이 같은 값을 다시 한번 설정해요. 둘 다 소스가 src/data/posts.js 하나라 지금은 어긋날 일이 없는데, 만약 프리렌더 스크립트가 안 돌아간 채로 배포되면 정적 파일은 예전 데이터, 클라이언트 렌더링은 최신 데이터를 보여주는 불일치가 생길 수 있어요. 이 위험 때문에 빌드 스크립트를 postbuild 훅으로 묶어놨어요. 빌드 명령 한 번으로 두 단계가 항상 같이 돌아가게요.