리액트 SEO 작업하기

개미·2023년 8월 14일
2
post-custom-banner

리액트 SEO 작업하기


프로젝트를 진행하면서 SEO 적용의 필요성을 느끼게 되었다. 리액트 프레임워크는 SEO에 취약한 점이 많아서 신경써야 할 부분이 많았다. 그 과정을 기록하고자 한다.

1. SEO가 무엇인가?

SEO는 Search Engine Optimization으로, 검색엔진 최적화라는 의미다. 대부분의 사용자들은 웹사이트에 들어오기 위해 검색엔진 (구글, 네이버)에 검색을 한다. 이때 검색엔진 결과 페이지에서 웹사이트 노출도를 높이는 작업을 SEO라고 한다. SEO가 잘되어 있으면, 마케팅비를 크게 줄일 수 있다. 검색엔진이 대신 마케팅을 해주니까 말이다.

2. 리액트 SEO 작업

2-1. google search console

google search console은 Google에서 무료로 제공하는 서비스로, 사용자가 사이트의 Google 검색결과 인지도를 모니터링하고 관리하며 문제를 해결하도록 도와준다.

google search console에서 해야할 작업:

  • 페이지 색인 요청
  • 사이트맵 추가
  • 코어 웹 바이탈

여기서 리액트 프로젝트의 사이트맵을 만들고 싶다면, react-router-sitemap 라이브러리를 이용해야한다.

2-2. react-router-sitemap

설치

npm install react-router-sitemap

sitemapRoutes.js

src 폴더 안에 생성

import React from 'react';
import { Route } from 'react-router';

export default (
    <Route>
        <Route path="/"  />
        <Route path="/home" />
        <Route path="/paperstorage" />
        <Route path="/history" />
        <Route path="/userprofile" />
        <Route path="/paper" />
        <Route path="/historypaper" />
        <Route path="/historytrash"  />
    </Route>
);

sitemapGenerator.js

src 폴더 안에 생성

require("babel-register")({
    presets: ["es2015", "react"]
});

const router = require("./sitemapRoutes").default;
const Sitemap = require("react-router-sitemap").default;

function generateSitemap() {
        return (
            new Sitemap(router)
                    .build("도메인")
                    .save("./public/sitemap.xml")
        );
}

generateSitemap();

sitemapGenerator.js 실행 및 배포

설치

npm install --save-dev babel-cli 
npm install --save-dev babel-preset-es2015 
npm install --save-dev babel-preset-react 
npm install --save-dev babel-register

package.json 수정

... 
  "scripts": {     
    ...     
    "sitemap": "babel-node src/sitemapGenerator.js",
    "predeploy": "npm run sitemap"  
  } 
...

npm run sitemap 실행 시 public 폴더에 sitemap.xml 생성이 된다.
사이트맵의 경로는 도메인/sitemap.xml이다. 이를 google search console에서 제출해주면 된다.

2-3. robots.txt

robots.txt 파일은 검색엔진 크롤러가 크롤링해도 되는 혹은 하면 안되는 페이지가 무엇인지 알려주는 파일이다. 이 파일은 public 폴더 내에 위치한다.

User-agent: *
Disallow:

Sitemap: 도메인/sitemap.xml

2-4. react-helmet

리액트는 SPA(single page application)이다. 여러 페이지를 가지는 경우 router를 이용한다. 이 점으로 인해 SEO에 취약한 점이 있다. 하나의 페이지 정보(public/index.html)만 읽어 각각의 페이지에 대한 정보를 읽지 못한다. 따라서 react-helmet을 이용하여 각 페이지마다, 메타태그를 설정해주어야 한다.

설치

npm install react-helmet-async

사용법

index.tsx

import { HelmetProvider } from 'react-helmet-async';
const rootElement = document.getElementById('root');
const app = (
    <React.StrictMode>
        <BrowserRouter>
            <HelmetProvider>
                <App />
            </HelmetProvider>
        </BrowserRouter>
    </React.StrictMode>
)
let root = ReactDOM.createRoot(rootElement as HTMLElement);
  • default 메타태그
    app.tsx의 return 내에
<Helmet>
   <title>타이틀명</title>
   <meta name="description" content="페이지 설명" />
</Helmet>
  • 메타태그 관리 파일 만들기
    SEOMetaTag.tsx
import { Helmet } from 'react-helmet-async';

const MetaTag = (props: any) => {
    return (
      <Helmet>
        <title>{props.title}</title>

        <meta name="description" content={props.description} />
        <meta name="keywords" content={props.keywords} />

        <meta property="og:type" content="website" />
        <meta property="og:title" content={props.title} />
        <meta property="og:site_name" content={props.title} />
        <meta property="og:description" content={props.description} />
        <meta property="og:image" content={props.imgsrc} />
        <meta property="og:url" content={props.url} />

        <meta name="twitter:title" content={props.title} />
        <meta name="twitter:description" content={props.description} />
        <meta name="twitter:image" content={props.imgsrc} />

        <link rel="canonical" href={props.url} />
      </Helmet>
    );
};

export default MetaTag;

각 페이지 tsx 파일

import MetaTag from "../SEOMetaTag";

return (
  <>
    <MetaTag title="타이틀명" description="페이지 설명" keywords="키워드"/>
    ...
  </>
)

2-5. react-sanp

SPA는 빠르게 앱을 빌드할 수 있지만, 페이지의 컨텐츠가 자바스크립트 태그에 막혀 크롤러에게 보여지지 않는다. 따라서 pre-rendering으로 페이지 요청을 낚아채어 사용자가 크롤러인지 여부를 확인하여 크롤러인 경우 페이지의 모든 요소를 사전 로드 한 페이지를 전달한다.
pre-rendering을 위해 react-snap 라이브러리를 사용할 수 있다. react-snap은 페이지의 일정시간이 지나면 html만 크롤링 해 static 파일로 만들어준다.

설치

npm install react-snap

사용법

index.tsx

const rootElement = document.getElementById('root');
const app = (
    <React.StrictMode>
        <BrowserRouter>
            <HelmetProvider>
                <App />
            </HelmetProvider>
        </BrowserRouter>
    </React.StrictMode>
)
let root = ReactDOM.createRoot(rootElement as HTMLElement);

if (rootElement?.hasChildNodes()) {
    // 이미 child nodes가 있는 경우, 기존 root를 사용하여 업데이트
    root.render(app);
} else {
    // child nodes가 없는 경우, root를 render로 초기화
    root.render(app);
}

package.json

...
"scripts": {
    ...,
    "postbuild": "react-snap"
},
"reactSnap": {
  "source": "./dist",
  "puppeteerArgs": [
    "--no-sandbox",
    "--disable-setuid-sandbox"
  ],
  "include": [
    "/home",
    "/paperstorage",
    "/history",
    "/userprofile",
    "/paper",
    "/historypaper",
    "/historytrash"
  ]
},
...
"homepage": "."
...

여기까지 완료했지만, npm run build시 Error: spawn Unknown system error -86 가 난다. 열심히 구글링하고.. gpt에 물어봐도 고쳐지지가 않아 일단 보류해두었다. (추후 수정 필요)

(2023.08.20 수정)
해당 에러에 대하여 깃헙 이슈를 열심히 뒤져본 결과, puppeteer이 mac os에서 제대로 작동하지 않는 것 같았다. Rosetta를 설치 하였더니 해결이 되었다!

softwareupdate --install-rosetta

그리고 package.json에서
homepage: "도메인"으로 바꾸었다. 이때 주의사항은 도메인 앞에 /(슬래시)를 넣어야되고 index.html에서 다음 코드를 head 태그 안에 삽입해야 에러가 나지 않는다!

<!--index.html-->
<base href="/" />

3. 끝

SPA의 SEO 취약점을 보완하기 위해 다양한 리액트 라이브러리를 이용해 검색엔진 최적화를 시도해보았다. 그 결과 코어 웹 바이탈을 돌려보았을 때 검색엔진 최적화가 82점에서 100점으로 올랐다.

그리고 네이버 Search Advisor에도 사이트 및 사이트맵 등록을 해두었다.

레퍼런스

https://ujeon.medium.com/react-리액트-웹사이트-사이트맵-만들기-1116ff8b6c2a
https://headwing.tistory.com/77
https://seo.tbwakorea.com/blog/robots-txt-complete-guide/
https://velog.io/@miyoni/noSSRyesSEO
https://github.com/remix-run/react-router/issues/3457
https://github.com/stereobooster/react-snap
https://onethegarden.github.io/blog/react-seo/
https://github.com/badsyntax/react-snap-example/blob/master/README.md#building-for-relative-paths

profile
개발자
post-custom-banner

0개의 댓글