react router와 서버 쪽의 router를 동시에 적용하면 어떤 라우터가 적용될까?
react router는 클라이언트 상에서 라우팅 처리를 하게 만들어주는 라이브러리입니다. 이것을 사용하여 url을 바꾸면 브라우저 상에서는 url이 바뀌지만 서버에는 어떠한 요청도 보내지 않습니다. 따라서 서버에서는 클라이언트상에서 url을 바꿨다는 사실을 알지 못합니다.
그런데 react router를 쓰더라도 서버 쪽에서도 라우터는 필요합니다. 그러면 react router를 사용하는 경우, 어떨 때 react router가 적용되고 어떨 때 서버의 router가 적용되는 걸까요? 이것은 사실 SPA(Single Page Application)의 개념을 이해하는 것과 깊은 관련이 있는 이슈입니다.
SPA는 단 한 개의 HTML 파일만 가지고 있으며 어떤 url로 요청이 들어와도 그 하나의 파일만으로 응답합니다. 일단 사용자가 HTML 파일을 받게 되면, 이후 어떠한 이벤트에 의해 바뀌는 url은 클라이언트상에서 처리가 되고 서버에 요청을 보내지 않습니다. 서버와는 오직 ajax를 이용해 데이터만 주고받을 뿐이죠. 즉, 이경우 첫 번째 라우팅 처리는 서버에서 이루어지고 이후 라우팅 처리는 클라이언트에서 react router에 의해 이루어집니다.
그런데 이미 HTML 파일을 받아 웹사이트에 접속된 상태에서 사용자가 필요한 url을 주소창에 직접 입력하는 경우에는 어떻게 될까요? 이때에는 주소창에 직접 입력한 것이기 때문에 react-router가 처리하는 것이 아니라 서버에 요청을 보내게 됩니다. 따라서 서버 측에서 준비된 router에 의해 처리를 받습니다.
따라서 모든 url에 대해 react-router에서도, 서버의 router에서도 준비를 해야 합니다.
주소창에 직접 입력하는 url은 클라이언트단인 react-router가 아니라 서버로 요청을 보냅니다. 그런데 서버에서 요청받은 url에 대한 어떠한 처리 로직도 준비가 되어있지 않으면 응답해줄 파일이 없고 이에 따라 클라이언트는 어떠한 파일도 받지 못해 오류가 발생합니다. 물론 서버 연동 없이 react 프로젝트를 테스트하면 주소창에 url을 직접 입력해도 react-router가 잘 동작하지만 이것은 애초에 서버에 request 자체를 날리지 않고, 기존에 가지고 있는 html 파일을 그대로 사용하기 때문에 가능한 것입니다.
따라서 가능한 모든 url에 대해 react-router를 이용해 SPA를 구현하고, 서버에서도 어떤 url로 요청이 들어와도 동일한 html 파일을 보내도록 라우팅 처리를 해야 합니다. 그렇게 동일한 html 파일을 응답받았으면, 클라이언트에서는 주소창에 입력된 url에 따라 react-router가 2차적으로 동작해서 주소에 맞게 렌더링 하게 됩니다.
react-router는 어떻게 url을 조작하는 것일까?
UI를 통한 링크 이동은 잘 처리되지만 url을 주소창에 직접 입력하면 react-router는 기본적으로 동작하지 않습니다. 왜냐면 react-router는 클라이언트에서 url을 이동한 것처럼 눈속임하고 렌더링 하는 장치이지, 실제로 url을 이동을 하는 것이 아니기 때문입니다. 즉 react-router는 실제로 url을 조작하는 게 아닙니다. 그저 주소창에 값을 띄워 보여줄 뿐입니다.
예제: react-router를 사용할 때 express에서 router 만들기
개념은 앞에서 설명했으니 코드를 보여드리겠습니다. 서버에 요청이 들어오면 express의 router를 이용해 동일한 HTML 파일을 보내주면 됩니다.
예제에서 url구조는 루트 / 와 /help 단 두 개만 존재하고 나머지는 Not Found 예외처리를 하였습니다.
express서버를 여는 server.js부터 보겠습니다.
<server.js>
const express = require('express');
const routes = require('./routes.js');
const app = express();
const port = process.env.PORT || 4000;
app.use(routes); //route 적용하기
app.listen(port, () => {
console.log('####Express listening on port####', port);
});
라우팅 처리를 담당하는 routes.js 파일은 따로 만들었습니다.
<routes.js>
const express = require('express');
const path = require('path');
const router = express.Router();
//html file
const index = path.resolve(__dirname, '../../dist/index.html');
router.get('*', (req, res) => {
res.sendFile(index);
});
module.exports = router;
이렇게 하면 어떤 url요청이 들어와도 index.html 파일을 뿌려줍니다. 그러면 클라이언트에서는 index.html 파일을 받은 후 알아서 url값에 따라 react-router에 의해 알맞게 렌더링 합니다.
여기서 추가적으로 라우터를 추가할 때 주의할 점은 *경로로 해놓은 코드가 항상 맨 아래에 있어야 한다는 것입니다.
*은 마지막 예외를 처리하기 위해 존재하기 때문입니다.
가령 예를 들면 다음과 같은 코드는 오작동합니다.
<routes.js>
const express = require('express');
const path = require('path');
const router = express.Router();
//html file
const index = path.resolve(__dirname, '../../dist/index.html');
router.get('*', (req, res) => {
res.sendFile(index);
});
router.post('/send', (req, res) => {
//처리로직
});
module.exports = router;
이렇게 하면 /send로 요청을 보냈음에도 *에서 먼저 요청을 받아서 index.html을 보내는 코드가 실행되고 뒤에 있는 라우터는 실행되지 않게 됩니다. 당연히 에러가 발생하겠죠?
다음과 같이 *을 받는 라우터는 맨 밑으로 보내야 합니다.
const express = require('express');
const path = require('path');
const router = express.Router();
//html file
const index = path.resolve(__dirname, '../../dist/index.html');
router.post('/send', (req, res) => {
//처리로직
});
router.get('*', (req, res) => {
res.sendFile(index);
});
module.exports = router;
이제 /send로 요청을 보내면 제대로 작동할 것입니다.
최근댓글