리액트에서의 SSR, SEO 처리 방법
# 리액트에서는 SSR, SEO 처리를 어떻게 해야 할까요?
2023년 12월 07일
개요
최근 Next.js를 이용하여 프로젝트를 진행하면서 서버 사이드 렌더링(SSR)과 검색 엔진 최적화(SEO)에 대해 깊이 공부하고 이를 실제로 적용해볼 수 있는 시간을 가졌다. Next.js의 SSR과 SEO 처리 방법에 대한 이해는 높아졌지만, 같은 리액트 생태계 내에서 이를 사용하는 리액트에서는 어떻게 처리하는지 궁금증이 생겼다. 특히, SSR과 SEO 처리가 과연 Next.js만의 전유물인것인지에 대해 알아보고 싶었다. 리액트에서의 SSR 및 SEO 처리 방법을 알아보며 어떠한 불편함이 있고 해당 불편함을 개선하기 위해 Next.js가 프레임워크적 성격으로 해당 불편함을 보완하고 있는지 Next.js의 강점을 느껴보는 시간을 갖고자 한다.
이 글에서는 리액트에서 SSR과 SEO를 어떻게 구현할 수 있는지에 대해 알아보고, 이를 통해 Next.js와의 차이점과 장점을 비교해보고자 한다. 리액트를 사용하면서도 SSR과 SEO에 대해 잘 이해하고 이를 리액트에서도 적절히 활용할 수 있도록 하는것이 목표이다.
React SSR 시작해보기
리액트에서 SSR을 구현하려면, 서버 사이드 렌더링 말 그대로 서버가 필요하다. 주로 Express와 같은 Node.js 서버와 함께 사용된다. 구현방법은 아래 순서대로 진행된다.
1. 프로젝트 초기 설정
먼저, 프로젝트 디렉토리를 생성하고 필요한 패키지를 설치한다.
기존 vite를 사용하여 리액트를 설치하는 것이 아닌, npm을 사용하여 리액트와 리액트 dom, Express 서버, 그리고 SSR을 구현하기 위한 Babel과 관련 패키지를 설치한다.
- react-dom-server: 서버 측에서 React 컴포넌트를 렌더링하는 데 사용되는 패키지며 서버에서 React 컴포넌트를 HTML 문자열로 렌더링할 수 있게 된다.
- @babel/core: 최신 자바스크립트와 JSX를 사용하기 위해 설치한다.
- @babel/preset-env: 최신 자바스크립트 기능을 사용할 수 있도록 하는 Babel 프리셋.
- @babel/preset-react: JSX를 컴파일하기 위해 필요한 Babel 프리셋.
- babel-loader: 웹팩과 함께 사용되어 자바스크립트 파일을 컴파일하는 역할을 한다.
💡 vite init을 통해 리액트를 설치하는 방식과 매우 달라서 관련 자료를 찾아보니, Vite를 사용하면 Babel 설정을 직접 관리할 필요 없이 기본적으로 ESBuild를 사용하여 최신 JavaScript 및 JSX 구문을 쉽게 사용할 수 있다고 한다.
2. Babel 설정
SSR을 위해 Babel을 설정해준다. Babel은 최신 자바스크립트 기능과 JSX를 컴파일할 때 사용되는데 babel.config.js
파일을 프로젝트 루트에 생성하고 다음과 같이 작성한다.
해당 설정은 1번에서 설치한 @babel/preset-env,@babel/preset-react을 사용하여 Babel이 ES6+와 JSX 문법을 컴파일할 수 있도록 해준다.
3. 리액트 컴포넌트 작성
SSR할 리액트 기본 컴포넌트를 작성한다. src/App.js
파일을 생성하고 다음과 같이 작성한다. API 호출까진 매우 어려울것 같아서, 간단한 h1컴포넌트를 출력하는것을 목표로 했다.
4. 웹팩 설정 (서버 번들)
SSR을 위해 서버와 클라이언트 번들을 생성하는 웹팩 설정 파일을 만든다. 먼저, webpack.server.js
파일을 생성하고 다음과 같이 작성한다.
path
모듈 로드
- Node.js의 기본 모듈인
path
를 사용하여 파일 및 디렉토리 경로를 사용한다.
엔트리 파일 (entry
)
- 번들링을 시작할 진입점을 지정하는데
./server.js
을 설정한다.
타겟 (target
)
- 웹팩이 번들을 어떤 환경에서 실행할 것인지를 설정하는데,
target: 'node'
는 생성된 번들이 Node.js 환경에서 실행될 것임을 의미한다.
- 번들링 과정에서 사용할 로더(특정 파일 형식 처리기)를 설정한다.
test: /\.js$/
: 모든.js
파일에 대해 아래의 로더를 사용하도록 설정use: 'babel-loader'
: Babel 로더를 사용하여 ES6+ 문법을 하위 호환되는 JavaScript로 변환exclude: /node_modules/
:node_modules
디렉토리 내의 파일은 Babel 로더의 처리 대상에서 제외한다.
5. Express 서버 설정
Express 서버를 설정하고 리액트 컴포넌트를 서버 사이드에서 렌더링하도록 코드를 작성한다. server.js
파일을 생성하고 다음과 같이 작성하면 된다.
Express 서버를 설정하고, 모든 요청에 대해 리액트 컴포넌트를 서버 사이드에서 렌더링하여 HTML 응답을 생성한다.
6. 웹팩 설정 (클라이언트 번들)
클라이언트 사이드 번들을 생성하는 웹팩 설정 파일을 생성한다. webpack.client.js
파일을 생성하고 다음과 같이 작성한다.
브라우저에서 로드되어 클라이언트 측에서 React 애플리케이션을 초기화하는 역할을 한다고 생각하면 된다.
클라이언트 파일을 작성한다. src/client.js
파일을 생성하고 다음과 같이 작성한다.
ReactDOM.hydrate(<App />, document.getElementById('root'))
: 서버에서 렌더링된 HTML과 일치하는 클라이언트 측 React 애플리케이션을 초기화한다.document.getElementById('root')
: React 애플리케이션이 렌더링될 DOM 요소를 지정한다. 일반적으로 main HTML의 root ID를 찾아 들어간다.hydrate
는 기존에 서버에서 렌더링된 마크업과 일치하는지 비교하여 이벤트 처리 및 상태를 보존한다.
7. 웹팩 실행 및 서버 시작
서버와 클라이언트 번들을 생성하기 위해 웹팩을 실행한다. 서버와 클라이언트 동시에 동작해야 하므로 package.json
스크립트를 수정하여 server단, client단 시작 빌드를 스크립트를 추가해야 한다.
이제 run build
통해 서버와 클라이언트 번들을 생성할 수 있다.
브라우저에서 http://localhost:3000
에 접속하면 SSR이 적용된다.
느낀점 및 정리
우선, React로 SSR을 직접 구현하는 과정에서 초기 설정과 환경 구성이 매우 어려웠다. 기존에는 vite init
을 사용하여 손쉽게 React를 설치하였는데, webpack부터 babel까지 직접 설치하려니 매우 불편했다.
Express와 같은 서버 라이브러리를 사용하여 React 애플리케이션을 서버에서 렌더링하도록 설정하는 과정 또한 상당히 어려웠고, 구글링하여 관련 자료를 찾아가며 설정할 수 밖에 없었지만, 해당 과정에서 웹팩과 바벨과 같은 빌드 도구들에 대한 이해도를 높일 수 있었다.
React로 SSR을 직접 구현해본 후, 코드가 변경될 때마다 빌드를 해줘야 한다는점과 항시 서버단 클라이언트단이 동시에 켜져있어야 한다는점을 통해 Next.js와 같은 프레임워크가 제공하는 장점들을 직접 느낄 수 있었는데, Next App Router에서 자동으로 SSR이 적용된다는게 얼마나 편한건지 느낄 수 있었다. 감사합니다 Vercel팀
근데 Vercel 팀은 돈이 어디서 나길래 전세계 수많은 프로젝트의 빌드 변경사항 및 서버를 실시간으로 그것도 무료로 제공하는지 궁금하다.
React SEO 시작해보기
1. 기본적인 HTML head 작성
React 애플리케이션에서 head 태그를 작성하여 SEO처리를 할 수 있다.
index.html 수정
React 애플리케이션의 public/index.html 의 head에 기본적인 메타 데이터를 추가한다.
하지만 해당 방식은 전체적인 프로젝트 메타 데이터만 포함되어 있을 뿐 페이지별 동적인 메타 데이터를 변경하지 못한다.
페이지별 동적 SEO 적용
따라서 각 페이지마다
useEffect
를 사용하여 head의 메타 데이터를 동적으로 변경해야 한다.
2. React Helmet 사용
React Helmet은 React 애플리케이션에서 동적으로 <head>
요소 내 메타 태그를 관리할 수 있게 해주는 라이브러리며 이를 통해 페이지의 meta 정보를 쉽게 제어할 수 있게 된다. 기존 index.html에서 직접 추가하는 방식이 아닌 React Helmet을 사용하여 SEO 처리를 해보자.
기본적인 사용 예제는 위 코드와 같이 작성할 수 있다. <Helmet>
컴포넌트 내에 SEO와 관련된 메타 태그들을 설정해주면 된다.
React Helmet을 사용한 페이지별 동적 SEO 적용
useEffect
와 React Helmet
을 사용하여 API를 통해 받아온 데이터를 통해 메타 태그를 각각의 페이지마다 동적으로 설정할 수 있다.
DynamicSEO
컴포넌트를 생성하고 해당 컴포넌트에서 title과 description을 주입 받을 수 있도록하고, 페이지가 로드될 때 받아온 해당 데이터를 사용하여 meta data를 생성한다.
💡 이제 DynamicSEO
컴포넌트를 사용하여 각 페이지별로 SEO 메타 태그를 동적으로 설정할 수 있게 되었다. DynamicSEO
컴포넌트는 children
을 받아서 자식 컴포넌트를 렌더링하며, title
과 description
을 props로 받아 Helmet
을 통해 SEO 태그를 설정한다. 물론 직접적으로 Props로 문자열을 넘겨줄 수 있지만, API를 통해 데이터를 받아올때도 해당 데이터에서 title
혹은 description
을 가공하여 좀 더 데이터를 통한 동적인 SEO 설정 또한 가능하다.
느낀점 및 정리
React는 클라이언트 사이드 렌더링을 기본으로 하기 때문에 직접적인 SEO 처리 코드가 필요하다. 페이지의 메타 태그(<title>
, <meta>
)를 좀 더 쉽게 동적으로 관리하려면 React Helmet과 같은 라이브러리를 사용해야 했지만, 이는 곧 JSX 내에서 메타 데이터를 추가하고 관리하는데 있어 추가적인 코드 작업을 필요로 했다.
또한 React Helmet을 사용한다고 해도, 동적인 페이지의 SEO 설정은 일일이 Props로 <title>
, <meta>
데이터를 직접 주입해야 했고, API를 사용하여 받아온 데이터를 title
, description
에 맞게 가공해야 하는 불편함이 느껴졌다.