SPA에서 뒤로가기, 새로고침 시 404 Not Found

Dansoon·2021년 12월 31일
6

SPA 새로고침시 404 Not Found 이유

React, Vue 등 js 기반의 프레임워크, SPA (Single Page Application)를 사용하면 발생하는 문제이다.
새로고침, 외부 링크를 타고 갔다가 다시 돌아오는 뒤로가기 등등 에서 경로를 찾기 못하는 경우가 많다.

이러한 오류를 connect-history-api-fallback 현상이라고 한다.

SPA는 일반적으로 웹 브라우저에서 액세스 할 수 있는 하나의 색인 파일(index.html)만 사용한다. 그런 다음 HTML5 히스토리를 사용하여 JavaScript를 사용하여 애플리케이션의 탐색을 인반적으로 처리 한다.

이로인해 사용자가 새로고침 버튼을 누르거나 방문 페이지 이외의 페이지에 직접 액세스 /help하거나 /help/online 웹 서버가 색인 파일을 무시하여 이 위치에서 파일을 찾을 때 문제가 바라생한다.
응용 프로그램이 SPA이므로 웹 서버는 파일을 검색하지 못하고 404 메세지를 사용자에게 반환한다.



해결 시도

1. connect-history-api-fallback 라이브러리

https://www.npmjs.com/package/connect-history-api-fallback

위 페이지에서는 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());

Options

라이브러리세 선택적으로 옵션을 전달할 수 있다.

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;
      }
    }
  ]
});

함수는 항상 다음 속성을 가진 개체로 호출된다.

  • parsedUrl: URL모듈의 url.parse.
  • match: 일치된 결과의 배결 Srtring.match(...)
  • request: HTTP 요청 객체

기록을 활성화하려면 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
})

2. node 기반의 express로 CRA를 띄우는 방법

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에서는 변경된 주소 및 새로고침을 해보아도 라우터가 정상 작동되는 것을 확인할 수 있다.

3. HashRouter 사용

import {HashRouter} from 'react-router-dom';

ReactDom.render(
	<Provider store={store}>
  	  <HashRouter>
  	  	<App/>
      </HashRouter>
	</Provider>
	document.querySelector('#root')
);
  • HashRouter
    • 주소에 해쉬(#)이 붙음
    • 새로 고침해도 그대로 나옴 ->#뒤에는 화면에서 읽는 경로이기 때문
    • 검색엔진으로 못읽는 단점때문에 거의 안씀

hashHistory 가 작동하고 browserHistory가 실패하는 이유

서버에 GET요청을 보내지 않고도 앱이 URL을 조작 할 수 있기 떄문이다.

4.historyApiFallback:true 추가하기

devServer:{
	contentBase: path.join(__dirname, 'dist'),
    compress: true,
    port: 3000,
    historyApiFallback: true
}

설명
http://localhost:3000/에 대한 GEThttp://localhost:3000/user 에 대한 GEThttp://localhost:3000/로 fallback하고 react-router/user을 처리 해줘야 한다. 위와 같이 해주려면
history api fallback이라는 webpack기능을 구현해야 한다.

  1. 방법 1
    package.json의 scripts 아래와 같은 설정 추가

"start":"webpack-dev-server --inline --content-base . --history-api-fallback"

  1. 방법 2
    dev server 에서 처리하도록 webpack.config.js 파일을 변경
devServer :{
  historyApiFallback: true,
  ...other stuff..
},

historyApiFallback option의 기능

HTML5의 history API를 사용하는 경우에 설정해놓은 url 이외의 url 경로로 접근했을때 404 responses를 받게 되는데 이때도 index.html을 서빙할지 결정하는 옵션이다. Reactreact-router-dom을 사용해 프로젝트를 만들때도 react-router-dom이 내부적으로 HTML5 History API를 사용하므로 미지정 경로로 이동했을때, 있는 경로지만 그 상태에서 refresh를 했을때와 같은 경우에도 애플리케이션이 적절히 서빙될 수 있어서 유용한 옵션이다.

원활하게 해결될때 까지 정보를 업데이트할 예정입니다.
피드백은 댓글로 부탁드립니다. 감사합니다.

profile
front engineer🧑🏻‍💻

0개의 댓글