-
Notifications
You must be signed in to change notification settings - Fork 1
React Router Client Middleware
React Router는 Declarative, Data, Framework 세가지 모드를 제공해요. Declarative 모드는 기본적인 라우팅에 대한 기능만 제공하고, Data 모드는 fetch, redirect 같은 데이터를 관리하는 기능도 제공해요. 마지막으로 Framework 모드는 SSR을 제공하고, 말그대로 라이브러리보다 프레임워크에 가까운 경험을 제공
유저가 주소창에 /abc 라우트를 직접 입력하면 브라우저는 서버에 GET /abc 요청을 보냅니다. SPA 환경에서는 개발 서버와 배포 서버가 이 요청을 그대로 처리하지 않고, 해당 경로에 실제 파일이나 서버 엔드포인트가 없을 때 앱의 진입점인 index.html을 반환하도록 설정되어 있습니다. 개발 환경에서는 Vite 개발 서버가 SPA 기본 동작으로 index.html을 fallback으로 반환하고, 배포 환경에서는 vercel.json의 rewrites 설정에 따라 /abc 같은 경로 요청을 index.html로 rewrite하도록 구성할 수 있습니다. Refit 프로젝트는 개발 서버와 배포 서버 모두에서 이런 방식으로 index.html을 fallback으로 반환하도록 되어 있습니다.
이때 중요한 점은 주소창의 URL을 /로 바꾸는 redirect가 발생하는 것이 아니라, 주소창은 /abc로 그대로 둔 채 서버 응답으로 index.html만 받아오는 rewrite가 동작한다는 점입니다. index.html이 로드되면 그 안의 script 태그를 통해 엔트리 파일인 main.tsx가 실행되고, main.tsx에서 React 앱이 마운트되면서 React Router가 초기화됩니다. 이후 React Router는 현재 브라우저의 URL(/abc)을 기준으로 라우트를 매칭하고, 해당 라우트 화면을 클라이언트에서 렌더링합니다.
전통적인 서버 미들웨어가 HTTP 요청을 가로채는 것과 유사하게, 클라이언트 미들웨어는 브라우저 내부의 내비게이션 과정을 가로챕니다. 페이지를 이동할 때마다 인증 체크, 로깅, 데이터 가공 등의 작업을 개별 페이지(loader)마다 작성하지 않고 한곳에서 처리할 수 있습니다. loader가 실행되기 **전(Before)**과 후(After) 모두에 관여할 수 있습니다. 위 예제 코드의 await next()가 그 증거입니다.
구글 로그인에 성공했고, 회원가입까지 완료한 회원이 아니면 /dashboard에 접근했을 떄 모두 /login으로 리다이렉트. ESM에서 모듈의 top-level variable은 Module Environment Record에 저장됨. initialAuthCheckPromise는 새로고침하지 않은 한 계속 유지됨.
let initialAuthCheckPromise: Promise<AuthSessionStatus> | null = null
const validateAuthOnce = async (): Promise<AuthSessionStatus> => {
const sessionStatus = getAuthSessionStatus()
if (sessionStatus !== 'unknown') return sessionStatus
/*
Promise에 대한 in-flight 캐시
*/
if (initialAuthCheckPromise) return initialAuthCheckPromise
initialAuthCheckPromise = (async () => {
try {
const response = await reissue()
const isNeedSignUp = response.result?.isNeedSignUp ?? true
/*
isNeedSignUp이 false인 경우는 구글 가입 완료 && 서비스 회원가입 완료
isNeedSignUp이 true인 경우는 구글 가입 완료 && 서비스 회원가입 미완료
*/
if (isNeedSignUp === false) {
markAuthenticated()
return 'authenticated'
}
markSignupRequired()
return 'signup_required'
} catch (error) {
markUnauthenticated()
console.error(error)
return 'unauthenticated'
} finally {
initialAuthCheckPromise = null
}
})()
return initialAuthCheckPromise
}-
[홍지운, 황주희] Claude Code 개념부터 활용까지 (Harness, Context, Skills)
-
[홍지운, 황주희] n8n과 Orval을 이용한 OpenAPI Specification(OAS) 주도 개발
-
[홍지운, 황주희] 코드 스플리팅을 통한 초기 로딩 속도 최적화
-
[홍지운, 황주희] React Router 기반 라우팅 처리
-
[황주희] PDF.js 기반 하이라이트 기능 설계하기 (Part 1)
-
[황주희] PDF.js 기반 하이라이트 안정화하기 ‐ 렌더링과 네트워크 이슈 해결 (Part 2)
-
[권찬] 질문 카테고리 분석을 위한 클러스터링 로직 설계 과정
-
[송영범] S3 Presigned‐URL 무한 업로드 문제
-
[송영범] 의도하지 않은 DB 커넥션 점유하는 현상을 발견 및 해결 과정
-
[이장안] VectorDB 도입 트러블슈팅: 객체지향적 Repository 인터페이스 설계 및 구현체 작성
-
[이장안] Spring Security 없이 완성하는 OAuth2 기반 JWT 인증 아키텍처