Tomcat 8 - React App URL Rewrite

Kim Jimin
KimJimin & Company
Published in
5 min readJan 23, 2019

우리 회사에서는 해마다 조금씩 더 많은 React App 프로젝트를 진행하고 있습니다.

Next.js 등 SSR(서버 쪽에서 렌더링) 기반을 채택하지 않고 간단히 create-react-app 패키지를 사용해서 앱을 생성하는 일도 많습니다. SEO(검색엔진 최적화)가 필요한 게 아니라면 SSR 없이 단순한 구조도 좋습니다.

우리 회사에 선택권이 주어진다면 React App의 정적 파일 모음(index.html, JavaScript, CSS 등)을 브라우저에 전달하기 위한 웹 서버는 Nginx를 택하는 편입니다. 빠르고, 가볍고, 설정하기 간편하기 때문입니다.

Nginx를 채택하든 Apache 등 다른 웹 서버를 선택하는 URL Rewrite 액션이 필요할 때가 있습니다. 사용자가 React App의 첫 화면이 아닌 서브 페이지에서 브라우저를 새로고침하거나 누군가에게서 서브 페이지 URL을 전달받아 접속하는 경우입니다.

이때 단순히 정적 파일 모음을 있는 그대로 내려주는 웹 서버 상태라면, 404 에러가 발생합니다. 실제로 웹 서버에 있는 파일 경로는 아니기 때문입니다. 하지만 첫 화면에 접속해서 React App 내부의 링크를 통하면 해당 서브 페이지는 열립니다.

서브 페이지에 바로 접속해도 화면이 잘 열리게 하려면 서브 페이지에 접속했어도 첫 화면(index.html) 접속 때와 같은 리소스를 제공하면 됩니다. 그러면 React Router가 알아서 페이지 준비 후 서브 페이지를 열어 줍니다.

이 액션을 위해 Nginx에서는 이렇게 합니다.

# nginx conf file
server {
location / {
try_files $uri /index.html;
}
}

try_files 설정 하나만 사용하면 URI에 자원이 있으면 그대로 반환, 없으면 루트의 index.html을 반환합니다. 단순해서 정말 좋습니다.

하지만 Tomcat을 최전방 웹 서버로 사용하면 조금 복잡합니다.

우선 명시하지 않으면 기본 작동하지 않는 밸브를 하나 명시해야 합니다.

# /opt/tomcat/conf/server.xml
<Host>
<Valve className="org.apache.catalina.valves.rewrite.RewriteValve" />
# 보통 이 자리에 기본 액세스 로그 밸브가 하나 명시되어 있습니다.
</Host>

이어서 webapps 바로 아래에 React App이 있다면 이렇게.

cd webapps/WEB-INF/ && touch rewrite.config

webapps 아래 somereactapp 아래에 있다면 이렇게 이동해서 rewrite.config 파일을 만듭니다.

cd webapps/somereactapp/WEB-INF/ && touch rewrite.config

그리고 열어서 다음 내용을 넣고 톰캣을 재시작하면 됩니다.

RewriteCond %{REQUEST_URI} ^/(css|img|js).*$
RewriteRule ^.*$ - [L]

RewriteRule ^.*$ /index.html [L,QSA]

RewriteCond는 RewriteRule 앞에 명시되며 이런 조건에 맞으면 다음 Rule을 적용해 달라는 뜻입니다. 맨 마지막엔 Rule만 하나 있는데 이도저도 아니면 이 Rule을 적용합니다.

현재 RewriteCond에 있는 조건식은 요청 경로에 css, img, js 중 하나의 디렉터리 경로가 포함되어 있으면 실제 존재하는 파일을 제공하는 것을 의미합니다. 마지막 Rule은 무조건 index.html을 제공하는 것을 의미하므로 실제 존재하지 않는 파일을 찾으면 index.html을 제공합니다.

Nginx 설정보다 꽤 복잡하고, rewrite 밸브 자체가 Tomcat 8부터 제공되므로 제약이 있습니다.

그리고 위 사례처럼 단순하게 webapps 아래에 하나의 앱이 바로 구동하는 게 아니라, /opt/tomcat/conf/Catalina/localhost 아래에 여러 서브 앱별로 xml 파일을 선언하는 경우 앞서 정의한 방법만으로는 동작하지 않습니다. 이때는 Context 별로 밸브를 선언해야 합니다.

# webapps/somereactapp 디렉터리를 xml로 별도 선언하는 경우# /opt/tomcat/conf/Catalina/localhost/somereactapp.xml<?xml version="1.0" encoding="UTF-8"?>
<Context>
<Valve className="org.apache.catalina.valves.rewrite.RewriteValve" />
<!-- Default set of monitored resources. If one of these changes, the -->
<!-- web application will be reloaded. -->
<WatchedResource>WEB-INF/rewrite.config</WatchedResource>
<WatchedResource>${catalina.base}/conf/web.xml</WatchedResource></Context>

만일 React App 여러 개가 하나의 톰캣에서 구동된다면 앱마다 각각 이렇게 설정해야 하고 rewrite.config도 각각 생성해야 합니다. 이때 rewrite.config 내용은 앞서와 동일합니다.

전통적인 웹 개발 방식에서는 서버 로직과 웹 UI 쪽 정적 파일이 같은 서버에서 묶여서 제공되는 일(IIS, Tomcat 등)이 흔했는데, 이제 React처럼 클라이언트만 독립적으로 전담하는 방식이 등장하면서 정적 파일은 Nginx 같은 최적화된 서버가 담당하고 REST API만 Tomcat이나 PM2가 컨테이너 역할을 해 주는 것이 이상적일 것 같습니다. PM2에 Express 앱으로 React를 감싸서 위와 같은 기능을 대신할 수도 있지만, 아무리 Express가 성능이 좋아도 Nginx가 좀 더 빠르겠죠. :)

--

--