리액트는 대표적인 CSR이며, SPA이다.
따라서 리액트는 하나의 index.html을 두고, 자바스크립트를 활용하여 데이터만 변경한다.
사용자가 필요한 부분의 데이터만 변경하여 웹을 구성한다.
여러가지 장점이 있지만 단점도 존재한다.
대표적으로 꼽으면, 초기 로딩 속도가 느리다는 것과 SEO가 어렵다는 것이다.
초기 로딩 속도는 SPA의 특성상 초기 로딩 시 모든 자바스크립트 파일을 로드해야하기 때문에 사용자가 특정 페이지를 들어가지 않더라도 모든 파일을 다운 받아야한다. 따라서 로딩 속도가 SSR에 비해 느리다.
또 하나는 SEO인데, React는 하나의 index.html을 두고 있기 때문에 페이지 마다 페이지를 설명하는 메타태그를 설정하기 어렵다.
따라서 브라우저 크롤러가 페이지를 크롤링 할 때 모든 페이지의 정보를 가져올 수 없어 검색엔진최적화에는 좋지 않다.
이 외에도 다른 단점들이 존재하지만 이번에는 React를 사용한 프로젝트에서 SEO에 조금이나마 도움이 될 수 있도록 react-helmet-async
, react-snap
을 사용하는 방법을 기록한다.
npm install react-helmet-async
npm 또는 yarn으로 react-helmet-async를 설치한다.
이전에 프로젝트를 하면서 react-helmet을 사용했었는데, 이번에 다른 프로젝트를 하면서 다시 사용하려고 보니 react-helmet-async라는 것이 있었다. 우선 이 둘의 차이점을 보자.
아래의 react-helmet-async npm에서 확인 할 수 있었다.
해석해보면, 기존의 react-helmet은 thread-safe하지 않은 react-side-effect에 의존했다.
📌 thread-safe란?
여러 스레드로부터 동시에 접근이 이루어져도 프로그램의 실행에 문제가 없음을 뜻한다.
react-side-effect라는 패키지가 thread-safe하지 않는 것 같다.
실제로 react-helmet의 깃허브 코드의 package.json에 들어가서 코드를 보았다.
위와 같이 react-side-effect가 디펜던시로 추가 되어 있음을 확인할 수 있었다.
react-helmet-async의 package.json에는 react-side-effect가 없는 것을 확인할 수 있었다. 궁금한 사람은 직접 가서 보기!
실제로 react-helmet 같은 경우는 마지막 업데이트가 현재 기준으로 3년 전이다.
업데이트가 이루어지지 않고 있다.
그래서 이번 프로젝트에서는 react-helmet 대신에 react-helmet-async를 사용하기로 했다.
기본적인 사용 방법은 기존의 react-helmet과 비슷하다.
하지만 react-helmet-async를 사용하기 위해서는 HelmetProvider를 사용하여 App.js를 감싸주어야한다.
// index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { BrowserRouter } from 'react-router-dom';
import { HelmetProvider } from 'react-helmet-async';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<HelmetProvider>
<BrowserRouter>
<App />
</BrowserRouter>
</HelmetProvider>
);
reportWebVitals();
// App.jsx
import React, { useState } from 'react'
import './App.css';
import { Helmet } from 'react-helmet';
import TempComponent from './pages/TempConponent';
function App() {
const [isShow, setIsShow] = useState(false);
return (
<div className="App">
<Helmet>
<title>app title</title>
</Helmet>
{isShow&&<TempComponent />}
<button onClick={()=> setIsShow(!isShow)}>{isShow?"close":"show"}</button>
</div>
);
}
export default App;
위와 같이 사용하면 된다.
index.js에서 HelmetProvider를 사용하였고, App.jsx에서는 Helmet을 사용하여 페이지의 title을 변경할 수 있었다.
react-helmet-async를 통하여 각 페이지별 메타태그를 설정하는 방법을 알아보았다. 하지만 여전히 문제는 남아있다.
react-helmet-async를 사용한다해도 페이지별로 html 파일이 생성되는 것이 아니기 때문에 브라우저의 크롤러는 여전히 하나의 index.html을 바라보고있다.
그렇기 때문에 url을 공유하였을 때 동적으로 변한 meta 태그가 보이지 않는 등의 문제가 발생한다.
이 문제를 react-snap을 사용하여 해결할 수 있다.
react-snap을 사용하면 페이지별로 index.html 파일이 생성된다.
React를 사용하여 프로젝트를 한다면, react-snap과 react-helmet-async를 통해 SEO에 불리하다는 단점을 어느 정도는 극복할 수 있을 것이다.
또한 react-snap은 pre-rendering을 지원한다.
pre-rendering은 웹 크롤러가 볼 수 있도록 페이지의 모든 요소를 사전로드하는 프로세스를 의미한다.
pre-rendering은 페이지 요청을 낚아채어 사용자가 크롤러인지 여부를 확인하여 크롤러인 경우 캐시 된 버전의 페이지를 전달하여 준다. 반대로 크롤러가 아니라면 일반적인 페이지를 전달해준다.
즉, 크롤러를 위한 최적화 작업을 하는 것이 pre-rendering의 핵심이라고 할 수 있다.
하지만, pre-rendering은 server-side-rendering(SSR)과는 명백히 다르다.
react-snap을 이용한 pre-rendering은 단순히 빌드 시점에 렌더링된 화면을 마치 스크린샷을 찍듯이 크롤링한 것 뿐이기 때문에 어떠한 동작도 하지 않는다.
검색엔진이 봤을 때, '이 사이트에는 이런 컨텐츠가 있구나' 정도를 알려준다고 생각하면 된다.
SSR은 그 시점에 실제 유저에게 표시되는 정보를 서버에서 제공하는 것이다.
안녕하세요 이 글 덕분에 SEO 동적 메타 태그를 잘 넣을 수 있었습니다.
개발자님과 같이 개발에 애정을 갖고 또 발전하고 싶은 사람을 위해 서비스를 하나 만들었습니다.
https://understandev.com
링크에 들어가면 둘러보실 수 있습니다.
(저희 understand는 개발자님의 모든 길을 응원하는 본질을 갖고 있습니다.)