React, Vue 등 js 기반의 프레임워크, SPA (Single Page Application)를 사용하면 발생하는 문제이다.
새로고침, 외부 링크를 타고 갔다가 다시 돌아오는 뒤로가기 등등 에서 경로를 찾기 못하는 경우가 많다.
이러한 오류를 connect-history-api-fallback 현상이라고 한다.
SPA는 일반적으로 웹 브라우저에서 액세스 할 수 있는 하나의 색인 파일(index.html)만 사용한다. 그런 다음 HTML5 히스토리를 사용하여 JavaScript를 사용하여 애플리케이션의 탐색을 인반적으로 처리 한다.
이로인해 사용자가 새로고침 버튼을 누르거나 방문 페이지 이외의 페이지에 직접 액세스 /help하거나 /help/online 웹 서버가 색인 파일을 무시하여 이 위치에서 파일을 찾을 때 문제가 바라생한다.
응용 프로그램이 SPA이므로 웹 서버는 파일을 검색하지 못하고 404 메세지를 사용자에게 반환한다.
위 페이지에서는 npm을 통한 미들웨어를 사용해서 해결을 제시한다.
npm install --save connect-history-api-fallback
라이브 러리 가져오기
var history = require('connect-history-api-fallback');
이제 application에 미들웨어를 추가하기만 하면 된다.
var connect = require('connect');
var app = connect()
.use(history())
.listen(3000);
물론 Express와 함께 사용할 수도 있다.
var express = require('express')
var app = express();
app.use(history());
라이브러리세 선택적으로 옵션을 전달할 수 있다.
var middleware = history({});
history({
index: '/default.html'
});
요청 URL이 정규식 패턴과 일치하면 인덱스를 재정의한다.
정적 문자열로 다시 작성하거나 함수를 사용하여 요청을 변환할 수 있다.
history({
rewrites: [
{ from: /\/soccer/, to: '/soccer.html'}
]
});
또는 함수를 사용하여 더많은 요청을 제어할 수 있다.
history({
rewrites: [
{
from: /^\/libs\/.*$/,
to: function(context) {
return '/bower_components' + context.parsedUrl.pathname;
}
}
]
});
함수는 항상 다음 속성을 가진 개체로 호출된다.
기록을 활성화하려면 verbose옵션을 통해 기능을 활성화 할 수 있다.
history({
verbose: true
});
history({
logger: console.log.bind(console)
});
htmlAcceptHeaders
Accepts: HTML콘텐츠 요청을 일치시킬 때 요청 되는 기본 헤더를 재정의 한다. (Default: ['text/html', '/']).
history({
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml']
})
disableDotRule
위에서 언급한 규칙을 비활성화한다.
history({
disableDotRule: true
})
CRA
에서 fallback
현상을 해결방법을 검색해보았지만 CRA프로젝트를 eject
하여 별도에 서버 렌더링을 수정하는 글들이 많았다.
node 기반인 express로 CRA를 띄워보는 방법
CRA Project
|- package.json
|- /public
|-- index.html
|-- server.js
|- /src
|-- index.js
/public/server.js
const express = require("express");
const path = require("path");
const app = express();
app.use(express.static(__dirname));
app.get("*", function(req, res) {
res.sendFile(path.resolve(__dirname, "index.html"));
});
function handleListenLog() {
console.log("Server Starting...");
}
app.listen(9000, handleListenLog);
그런 다음, build
를 하고 build
된 위치에 가보면 방금 생성한 server.js
도 같이 배포된다.
터미널에서 server.js
를 실행해 보자
$ node server.js
express
로 띄운 CRA
에서는 변경된 주소 및 새로고침을 해보아도 라우터가 정상 작동되는 것을 확인할 수 있다.
import {HashRouter} from 'react-router-dom';
ReactDom.render(
<Provider store={store}>
<HashRouter>
<App/>
</HashRouter>
</Provider>
document.querySelector('#root')
);
서버에 GET
요청을 보내지 않고도 앱이 URL을 조작 할 수 있기 떄문이다.
devServer:{
contentBase: path.join(__dirname, 'dist'),
compress: true,
port: 3000,
historyApiFallback: true
}
설명
http://localhost:3000/
에 대한 GET
및 http://localhost:3000/user
에 대한 GET
은 http://localhost:3000/
로 fallback하고 react-router
는 /user
을 처리 해줘야 한다. 위와 같이 해주려면
history api fallback
이라는 webpack
기능을 구현해야 한다.
"start":"webpack-dev-server --inline --content-base . --history-api-fallback"
dev server
에서 처리하도록 webpack.config.js
파일을 변경devServer :{
historyApiFallback: true,
...other stuff..
},
HTML5
의 history API를 사용하는 경우에 설정해놓은 url 이외의 url 경로로 접근했을때 404 responses
를 받게 되는데 이때도 index.html
을 서빙할지 결정하는 옵션이다. React
와 react-router-dom
을 사용해 프로젝트를 만들때도 react-router-dom
이 내부적으로 HTML5 History API
를 사용하므로 미지정 경로로 이동했을때, 있는 경로지만 그 상태에서 refresh
를 했을때와 같은 경우에도 애플리케이션이 적절히 서빙될 수 있어서 유용한 옵션이다.
원활하게 해결될때 까지 정보를 업데이트할 예정입니다.
피드백은 댓글로 부탁드립니다. 감사합니다.