작년 여름에 MERN 스택 프로젝트를 진행했다. 개발 모드에서는 문제가 없었으나, 배포 후 CORS 문제가 생겨 제대로 작동하지 않아 시연 영상으로 대체하게 되었던 기억이 난다.
프로젝트 일정을 마치고나서 위와 같이 해결하지 못했던 문제가 아른거려
최근에 해당 프로젝트를 다시 열어 문제점들을 해결했다. (아직 CORS 문제 말고도 해결해야할 것들이 많다...)
이 글에서는 문제점을 해결하면서 새로 알게 되었던 점들을 정리해보려 한다.
개발 모드에서 API 서버와 통신하는 과정에서 아래와 같이 프론트엔드 package.json의 proxy 설정에 서버 URL을 설정해두었었다.
// ... 생략
"@types/dompurify": "^3.0.2",
"@types/styled-components": "5.1.26"
},
"proxy": "http://localhost:5001"
}
이렇게 설정함으로써 예를 들어 axios.get('/api/answers')로 요청을 보내면 'http://localhost:5001/api/answers' 로 요청을 보낼 수 있었다.
그런데 배포 이후 proxy를 배포된 서버 URL로 변경하여도 요청이 제대로 보내지지 않는 문제가 발생했다.
그 이유는 package.json의 proxy 설정이 개발 모드에서만 통하는 설정이기 때문이었다.
Keep in mind that proxy only has effect in development (with npm start)
이러한 사항은 CRA 공식 문서에도 위와 같이 명시되어 있었다.
이러한 프록시 설정은 클라이언트에서 보낸 요청을 개발 서버에서 프록시 서버로 전달하고, 프록시 서버가 해당 요청을 다시 실제 API 서버로 전달하는 방식으로 동작한다.
즉 요청을 대신 보내주는 포워드 프록시의 역할을 한다.
그런데 이 proxy 설정은 개발 모드에서만 작동한다! 개발 모드에서 CORS 에러를 방지하기 위해 우회하는 방법으로 사용한다고 한다.
그래서 배포에서도 서버 URL로 요청이 정상적으로 보내지도록 하기 위해
axios.create를 통해 만들어진 axios 인스턴스를 적용하여 요청을 보냈다.
const instance = axios.create({
baseURL: process.env.REACT_APP_SERVER_URL,
withCredentials: true,
headers: {
'Content-Type': 'application/json',
},
});
axios.create로 기본 instance를 설정하여 기존 axios.get('/api/answers') 형식에서 instance.get('/api/answers') 형식으로 api 요청 코드를 변경했다.
그런데 나중에 이러한 설정도 문제를 일으키는데...
No 'Access-Control-Allow-Origin' header is present on the requested resource.
그 후 대부분의 요청은 잘 이루어졌으나, 일부 PUT 요청(북마크, 좋아요 기능)에서 Preflight 에러가 발생했다.
const instance = axios.create({
baseURL: process.env.REACT_APP_SERVER_URL,
// withCredentials: true,
// headers: {
// 'Content-Type': 'application/json',
// },
});
결과적으로는 axios instance에서 credentials 설정과 Content-Type에 대한 설정 부분을 지우니 정상적으로 PUT 요청이 이루어졌다.
우선 우리 프로젝트에서는 토큰 관리에 쿠키를 이용하지 않았으므로 withCredentials를 true로 설정해줄 필요는 없었다. 그래서 해당 부분을 지웠다.
문제는 headers의 Content-Type을 application/json으로 지정해준 부분인데, 이 부분 때문에 문제가 발생했다.
저 headers 부분을 주석 처리하고 정상적으로 PUT 요청이 이루어졌을 때의 Content-Type은 application/x-www-form-urlencoded
였다.
Content-Type이 application/json
으로 설정된 axios 인스턴스로 bookmark PUT 요청을 보내면 에러가 났다.
왜 해당 PUT 요청에서만 Content-Type이 application/json
으로 설정되어있을 때 400 에러가 나는지는 아직 이유를 정확히 파악하지는 못했다.
알게 되면 다시 글을 추가하여 써 보겠다.
app.use(
cors({
origin: process.env.CLIENT_URL,
// credentials: true 삭제
})
);
또한 우리 프로젝트는 쿠키를 사용하지 않기 때문에 express 서버의 cors에 설정되어있던 credentials: true도 지워줬다.
기존에 .env 파일 하나로 관리되던 환경변수를 .env.development와 .env.production으로 나누어줬다.
하나의 .env 파일만 사용할 때는 프로덕션에서 테스트를 하다가 다시 개발 모드로 돌아갈 때마다 .env 파일을 수정해주어야 했다.
이러한 점이 불편해서 환경변수를 프론트엔드와 백엔드 모두 두 개의 파일로 나누어주었다.
.env.production
REACT_APP_SERVER_URL=배포된_서버_주소
.env.development
REACT_APP_SERVER_URL=http://localhost:5001
프론트엔드의 경우, 리액트가 npm start에서는 자동으로 .env.development의 환경변수를 적용해주고 빌드 시에는 .env.production을 적용해주는 것 같아서 어렵지 않았다.
.env.development
MONGO_URL=mongodb+srv://~
CLIENT_URL=http://localhost:3000
JWT_SECRET_KEY=시크릿키
.env.production
MONGO_URL=mongodb+srv://~
CLIENT_URL=배포된_클라이언트_주소
JWT_SECRET_KEY=시크릿키
백엔드는 이와 같이 설정해주었는데 프론트엔드와 달리 아래와 같이 NODE_ENV에 따라 적용될 환경변수파일을 지정해주어야 했다.
먼저 dotenv 패키지와 cross-env 패키지를 설치해야 한다.
npm install dotenv
npm install cross-env --save-dev
dotenv는 환경 변수를 설정하고 관리할 때 편리한 도구이고,
cross-env는 프로젝트를 다양한 환경에서 실행할 때 환경 변수를 쉽게 설정할 수 있도록 돕는 도구다. 예를 들어, Windows와 Unix 기반 시스템에서는 환경 변수 설정이 서로 다른데, cross-env를 사용하면 이런 차이를 신경쓰지 않고 환경 변수를 설정할 수 있어 개발할 때 유용하다.
package.json
// ... 생략
"scripts": {
"dev": "cross-env NODE_ENV=development nodemon index.js",
"start": "cross-env NODE_ENV=production node index.js"
},
// ... 생략
npm run dev를 실행할 때 NODE_ENV는 development로,
npm run start를 실행할 때 NODE_ENV는 production으로 이름 짓는다.
index.js
// NODE_ENV에 따라 적절한 .env 파일을 로드한다.
if (process.env.NODE_ENV === "development") {
dotenv.config({ path: "./.env.development" });
} else if (process.env.NODE_ENV === "production") {
dotenv.config({ path: "./.env.production" });
}
NODE_ENV가 development일 때는 .env.development 환경 변수를,
NODE_ENV가 production일 때는 .env.production 환경 변수를 적용하도록 한다.
그런데 index.js에만 설정할 게 아니라 환경변수를 사용하는 파일 모두에 위 코드를 넣어줘야 에러가 발생하지 않았다. 나는 환경 변수에 이미 설정되어 있는 JWT Secret Key가 없다는 에러가 자꾸 떠서 화가 났는데 이 환경변수를 사용하는 (index.js 외의) 다른 파일에도 위의 dotenv 코드를 넣어주니 에러가 사라졌다.
https://create-react-app.dev/docs/proxying-api-requests-in-development/
https://falsy.me/nodejs-express-%ED%86%B5%EC%8B%A0-cors-cors-pre-flight-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0/
https://velog.io/@public_danuel/process-env-on-node-js
https://soohey.tistory.com/4