diff --git a/.github/workflows/cleanup-preview.yml b/.github/workflows/cleanup-preview.yml
new file mode 100644
index 00000000..00006e6f
--- /dev/null
+++ b/.github/workflows/cleanup-preview.yml
@@ -0,0 +1,36 @@
+name: Cleanup-Preview
+
+on:
+ pull_request:
+ types: [closed]
+ branches: ['develop']
+
+jobs:
+ cleanup:
+ runs-on: ubuntu-latest
+ environment: DEPLOY_PREVIEW
+ permissions:
+ pull-requests: write
+
+ steps:
+ - name: Configure AWS Credentials
+ uses: aws-actions/configure-aws-credentials@v4
+ with:
+ aws-access-key-id: ${{ secrets.AWS_PREVIEW_ACCESS_KEY_ID }}
+ aws-secret-access-key: ${{ secrets.AWS_PREVIEW_SECRET_ACCESS_KEY }}
+ aws-region: ap-northeast-2
+
+ - name: Delete Preview from S3
+ run: |
+ aws s3 rm s3://${{ secrets.AWS_PREVIEW_BUCKET_NAME }}/pr-${{ github.event.pull_request.number }}/ --recursive
+
+ - name: Comment Cleanup Notice
+ uses: actions/github-script@v7
+ with:
+ script: |
+ await github.rest.issues.createComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: context.payload.pull_request.number,
+ body: '🧹 Preview 배포가 정리되었습니다.'
+ });
diff --git a/.github/workflows/deploy-preview.yml b/.github/workflows/deploy-preview.yml
new file mode 100644
index 00000000..086cc395
--- /dev/null
+++ b/.github/workflows/deploy-preview.yml
@@ -0,0 +1,100 @@
+name: Deploy-Preview
+
+on:
+ pull_request:
+ types: [opened, synchronize, reopened]
+ branches: ['develop']
+
+concurrency:
+ group: preview-${{ github.event.pull_request.number }}
+ cancel-in-progress: true
+
+jobs:
+ deploy-preview:
+ runs-on: ubuntu-latest
+ environment: DEPLOY_PREVIEW
+ permissions:
+ pull-requests: write
+ contents: read
+
+ steps:
+ - name: Setup NodeJS
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+
+ - name: Checkout Code
+ uses: actions/checkout@v4
+
+ - name: Setup .env
+ run: |
+ echo "${{ vars.ENV }}" > .env
+ echo "${{ secrets.ENV }}" >> .env
+ echo "VITE_BASE_PATH=/pr-${{ github.event.pull_request.number }}" >> .env
+
+ - name: Install Dependencies
+ run: npm ci
+
+ - name: Build Preview
+ run: npm run build
+
+ - name: Configure AWS Credentials
+ uses: aws-actions/configure-aws-credentials@v4
+ with:
+ aws-access-key-id: ${{ secrets.AWS_PREVIEW_ACCESS_KEY_ID }}
+ aws-secret-access-key: ${{ secrets.AWS_PREVIEW_SECRET_ACCESS_KEY }}
+ aws-region: ap-northeast-2
+
+ - name: Deploy to S3 Preview Bucket
+ run: |
+ aws s3 sync ./dist s3://${{ secrets.AWS_PREVIEW_BUCKET_NAME }}/pr-${{ github.event.pull_request.number }}/ --delete
+
+ - name: Invalidate CloudFront Cache
+ run: |
+ aws cloudfront create-invalidation \
+ --distribution-id ${{ secrets.AWS_PREVIEW_CLOUDFRONT_ID }} \
+ --paths "/pr-${{ github.event.pull_request.number }}/*"
+
+ - name: Comment Preview URL on PR
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const prNumber = context.payload.pull_request.number;
+ const domain = '${{ vars.PREVIEW_DOMAIN }}';
+ const url = `https://${domain}/pr-${prNumber}/`;
+
+ const body = `## 🚀 Preview 배포 완료!
+
+ | 환경 | URL |
+ |-----|-----|
+ | Preview | [열기](${url}) |
+ | API | Dev 환경 |
+
+ > PR이 닫히면 자동으로 정리됩니다.`;
+
+ const { data: comments } = await github.rest.issues.listComments({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: prNumber,
+ });
+
+ const existing = comments.find(c =>
+ c.user.login === 'github-actions[bot]' &&
+ c.body.includes('Preview 배포 완료')
+ );
+
+ if (existing) {
+ await github.rest.issues.updateComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ comment_id: existing.id,
+ body
+ });
+ } else {
+ await github.rest.issues.createComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: prNumber,
+ body
+ });
+ }
diff --git a/src/routes/routes.tsx b/src/routes/routes.tsx
index e19ae4ee..d6c9a899 100644
--- a/src/routes/routes.tsx
+++ b/src/routes/routes.tsx
@@ -102,28 +102,33 @@ const protectedAppRoutes = appRoutes.map((route) => ({
),
}));
-const router = createBrowserRouter([
+const router = createBrowserRouter(
+ [
+ {
+ element: (
+ <>
+
+
+ >
+ ),
+ children: [
+ {
+ path: '/',
+ element: ,
+ children: protectedAppRoutes, // 기본 언어(ko) 라우트
+ },
+ {
+ path: ':lang', // 다른 언어 라우트
+ element: ,
+ children: protectedAppRoutes,
+ },
+ ],
+ },
+ ],
{
- element: (
- <>
-
-
- >
- ),
- children: [
- {
- path: '/',
- element: ,
- children: protectedAppRoutes, // 기본 언어(ko) 라우트
- },
- {
- path: ':lang', // 다른 언어 라우트
- element: ,
- children: protectedAppRoutes,
- },
- ],
+ basename: import.meta.env.VITE_BASE_PATH || '/',
},
-]);
+);
// 라우트 변경 시 Google Analytics 이벤트 전송
router.subscribe(({ location }) => {