근 10일의 여정을 마무리하는 기념으로
신입의 삽질을 정리해보자!
현재 담당하는 서비스는 React로 개발되어 있다.
따라서 SPA 특성 상 처음 접속한 상태로 계속 사용하다보면 새로운 기능이 추가되거나 수정된 것을 알 수가 없다.
큰 수정이 아닐 때는 문제가 없었지만,
필요한 데이터가 바뀌었다던지 하는 경우에는 새로고침이나 재접속으로 신규 버전을 받아오지 않으면 수정한 부분과 충돌이 발생하기도 했다.
기존에는 서비스 운영 담당자분이 “새로고침 해보세요~” 하는 안내로 처리하고 있었지만 고객사가 늘어갈 수록 계속해서 그렇게 해결할 수만은 없는 일.
그러던 중 미션을 받았다.
이걸 위해서 크게 필요한 건 두 가지
흠... 일단
그런데… 우리 버전 정보가 뭘까?
처음엔 Git tag를 보고 아 저게 우리의 버전이구나! 해서 열심히 Git tag 읽어오는 방법을 찾았더랬다… (심지어 AWS Codecommit 쓰고 있어서 정보가 더 없었다.)
하지만!
알고 보니 따로 관리하는 버전 정보는 없었다!
그래서 일단 package.json에 소중히 작성된 버전을 앞으로 가져다 사용하기로 결정.
→ 버전업이 일어날 때마다 package.json에 버전 정보를 수정하면 된다.
→ 1.2.0 (메이저. 마이너. 패치) 버전 업이 일어날 예정이다.
(이 정도 삽질을 했을 때는 이미 며칠 날려먹은 뒤였다. 하지만 몰랐지 더 큰 삽질이 다가올 줄은)
관련해 여러 블로그를 찾아보는데 PWA 관련 한 포스팅이 가장 많았다. (PWA의 개념은 생략하겠다.)
일단 버전을 쓰고 읽기는 것조차 불가능한 상태였지만 일단 알림을 구현하는 부분에 있어서는 가장 먼저 생각한 부분이었다.
알림을 구현하는 건 크게 어렵지 않았는데(yes 알림 not PWA), 문제는 웹 알림…? 구석에서 언제 떴다 사라졌는지도 모르겠고 알림창을 누르기 전까진 확인할 수 없는 녀석? 우리에게 필요한 모습이라고는 볼 수 없었다.
Firebase의 큰 이점 중 하나인 플랫폼 상관없이 손쉽고 빠르게 알림을 줄 수 있다! 는 것은 나의 경우에는 오버스펙이었다. 따로 어플이 있지도 않고 굳이 버전 알림 하나만을 위해 서비스 의존도를 높일 필요는 없지 않겠는가. 따라서 파이어베이스도 마찬가지로 굳이의 아이콘이 되어 크게 찾아보지 않고 넘겼다.
다시 버전 저장의 문제로 돌아간다.
로컬에 그때그때 버전을 저장해놓고, 최신 Package.json의 버전과 비교해보면 되려나? 안 될 것 같긴 한데…
예. 역시나 안 됩니다.
이건 새로고침을 안 해도 Package.json의 최신값을 읽어올 수 있어야 가능 한 건데…
되겠냐?😭
그렇다면 어쩔 수 없이 다른 무언가를 통해야한다.
Amplify를 통해 자동 배포가 이뤄지는데, 그럼 배포가 되는 걸 Lambda 트리거로 삼아서 API를 하나 만들어서 보내면 안 되나!? 하는 AWS 알못의 헛된 망상.
(↑ 고민의 흔적 🥲)
AWS라곤 현재 사용 중인 CodeCommit과 S3(아주 조금) 그리고 요금 관리^^.. 만 사용해봤던 과거의 나.
그리고 이런 나였기에..^^ 회사 AWS IAM 계정엔 권한이 거의 없다. (당연히 안 쓰는 건 없는 게 좋겠지요.)
죽어가던 프리티어 계정 살려서 하나하나 만들어보면서 트리거로 쓸 수 있나 보는데,
생각보다 람다 트리거 자체에 내가 원하는 값을 걸 수도 없었을 뿐 아니라
그래서 결국 API는 무슨 값을 쏴줄 건데?의 도돌이표로 반복되는 근본적인 문제를 해결하지 못했다.
이 쯤 했을 때 다시 맹서치 시작… 웹팩의 플러그인을 통해 구현한 케이스를 찾았다. 하지만 우리 서비스? craco로 빌드한다! 크라코에 웹팩을 어떻게…?
하면서 머리 쥐어뜯고 있을 때 드디어 구원의 손길이 내려왔다.
“빌드할 때 버전 정보를 S3에 올려서 해당 주소로 버전 정보를 읽어보세요.”
그렇게 시작된 S3와의 씨름 시작.
이번엔 Amplify 권한(조금)과 S3 권한(조금)까지 야무지게 받아 왔다.
몇가지 삽질을 거쳐 만들어낸 S3 업로드 스크립트 (프로젝트 루트에 위치시켰다.)
// version-script.js
const AWS = require("aws-sdk");
const fs = require("fs");
// 환경 변수에서 AWS 액세스 키 및 시크릿 액세스 키 가져오기
const accessKeyId = process.env.REACT_APP_VERSION_ID;
const secretAccessKey = process.env.REACT_APP_VERSION_KEY;
// AWS 설정
const region = "ap-northeast-2"; // 리전 설정
AWS.config.update({ accessKeyId, secretAccessKey, region }); // AWS 구성 업데이트
const s3 = new AWS.S3();
// 버전 정보를 담은 텍스트 파일 생성
const version = require("./package.json").version;
fs.writeFileSync("version.txt", version);
// 버전 파일이 올바르게 생성되었는지 확인
console.log("Version file content:", version);
// S3에 파일 업로드
const uploadParams = {
Bucket: "(버킷명)",
Key: "version.txt",
Body: fs.createReadStream("version.txt"),
};
s3.upload(uploadParams, (err, data) => {
if (err) {
console.error("Error uploading file:", err);
} else {
console.log("Uploaded successfully:", data);
}
});
스크립트 실행은
// package.json
// 방법 1. build 명령어에 함께 작성
...생략
"scripts": {
"build:dev": "craco build && node version-script.js",
...생략
},
// 방법 2. postbuild 명령어로 작성
...생략
"scripts": {
"postbuild": "craco build && node version-script.js",
...생략
},
+) 만약 이 스크립트를 사용할 때 error가 발생한 다면 dotenv 패키지를 설치해보길…
갑자기 빌드했는데 index.html이 생성이 안 돼서 눈물만 하루 종일 좔좔 흘렸는데
알고 보니 환경 변수를 못 읽어서 권한 에러가 발생한 거였다. (콘솔 메세지를 잘 보자)
자 이렇게 무사히 파일을 올렸나요?
파일은 어떻게 읽을까요?
(이미지 업로드 url은 presignedUrl로 보내고 있어서 받을 때도 이걸로 받아야 하는 줄 알고 삽질 추가로 하고 있었다 촤하핫 그럼 지금까지 이미지를 어떻게 봤었냐? 촤하핫^_ㅠ )
얌전하게 같은 버킷에 있는 이미지 주소를 확인하든 사수에게 물어보든..^^; 해서 올바른 주소를 받아낸다.
const response = await fetch("(version.txt 경로)")
if (response.ok) {
const version = await response.text()
if (version && version !== package_version) {
window.location.reload()
}
} else {
console.error("Failed to fetch version:", response.status)
}
}
짜잔! 그럼 버전 정보를 올리고 읽어올 수 있습니다!!!!!!!!
하고 끝났으면 얼마나 좋았을까. 중간중간 갑자기 업로드가 안 된다든지 (그 env 못 읽었던 문제) + 테스트 링크에서는 cors에러 때문에 업로드가 안 된다든지 하는 문제가 발생했다.
이걸 고치고 끝! 하기엔
dev버전 배포 링크와 prod버전 배포 링크도 달라 버전 파일을 두개 만들거나, 내부에서 로직을 분리해야하는 과제가 남아있었다.
이런 저런 문제가 있다… 했더니 사수분이 다른 방식을 제안해줬다.
빌드할 때 만든 version.txt 파일을 S3가 아니라 빌드폴더에 생성하면 바로 읽을 수 있지 않냐고.
아..?
그렇다. 잊고 있었던 robots.txt
기본 주소/robots.txt 로 접속하면 해당 내용이 잘 나오지 않나!
아하!!!?!?!???
거기다 이미 배포한 주소로 요청을 보내는 거니까 새로고침과 관계 없이 최신 빌드 폴더에 요청을 보낼 수 있다!
결국 AWS는 잘 공부한 게 됐습니다! (오히려 좋아)
script파일은 오히려 필요가 없어졌고
아주 심플한 쉘 스크립트 명령어를 배워왔다.
// package.json
...생략
"scripts": {
"build:dev": "craco build && echo $npm_package_version > ./build/version.txt",
...생략
},
이렇게 build 파일 아래에 파일을 생성하고
fetch("/version.txt")
로 아주 간단하게 최신 버전 정보를 받아올 수 있게 됐다!
(만약 로컬에서 파일을 받아올 수 없다면, public 폴더에도 version.txt를 생성해주면 된다.)
그럼 이제 언제 어디서 어떻게 알림을 줄 건지만 정하면 된다!
호출을 매번 하기에는 그렇게 자주 일어나는 버전 업데이트가 아니고, 서비스 성능 저하를 야기할 수도 있다.
그렇다고 지연 응답을 구현하기에는 따로 응답을 주는 쪽을 다룰 수 있는 게 아니다.
우리 서비스를 생각해봤을 때,
위와 같은 상황이라 페이지 전환마다 호출하는 것도 같이 고려할까 했지만, 일단은 App이 실행될 때와 창이 재활성화 되었을 때에 한해 버전 확인 api를 호출하기로 했다.
// App.tsx
useEffect(() => {
// App 최초 실행 시 버전 체크
checkVersionUpdate()
// 페이지가 재활성화 된 경우 버전 체크
const handleVisibilityChange = () => {
if (!document.hidden) {
checkVersionUpdate()
}
}
document.addEventListener("visibilitychange", handleVisibilityChange)
return () => {
document.removeEventListener("visibilitychange", handleVisibilityChange)
}
}, [])
// utils/version.ts
const checkVersionUpdate = async () => {
if (process.env.NODE_ENV !== "production") {
// dev, prod 환경이 아닌 경우 버전 체크 미실행
return
}
const package_version = packageJson.version
const response = await fetch("/version.txt")
if (response.ok) {
const version = await response.text()
if (version && version.trim() !== package_version.trim()) {
// Alert 발생
}
} else {
console.error("Failed to fetch version:", response.status)
}
}
이 코드를 기반으로 새로운 사이드 알림 컴포넌트까지 하나 둑닥둑닥 만들고
챱챱 적용해보면!
짠! 버전 알림이 뜹니다!!!!
(새로고침 하기 전까지는 앱이 재활성화 될 때마다 해당 알림이 활성화 된다)