Skip to content

Commit 534e9a7

Browse files
authored
Merge pull request #110 from Team-Senifit/release-1.0.1
fix: NEXT_PUBLIC_SITE_URL 반영
2 parents 3b61e46 + 14324e6 commit 534e9a7

8 files changed

Lines changed: 105 additions & 139 deletions

File tree

.dockerignore

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
.git
2+
.gitignore
3+
.next
4+
node_modules
5+
npm-debug.log
6+
Dockerfile
7+
docker-compose.yml
8+
.DS_Store
9+
README.md
10+
docs
11+
stories
12+
scripts
13+
.storybook
14+
**/*.log

.github/workflows/deploy.yml

Lines changed: 47 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -9,136 +9,66 @@ concurrency:
99
group: deploy-develop
1010
cancel-in-progress: true
1111

12+
permissions:
13+
contents: read
14+
packages: write
15+
1216
jobs:
1317
build-and-deploy:
1418
runs-on: ubuntu-latest
1519
env:
16-
APP_DIR: /srv/senifit-front
17-
RELEASE_NAME: ${{ github.sha }}
18-
NODE_ENV: production
19-
NEXT_PUBLIC_API_BASE: ${{ secrets.NEXT_PUBLIC_API_BASE }}
20-
NEXT_PUBLIC_API_URL: ${{ secrets.NEXT_PUBLIC_API_URL }}
21-
API_PREFIX: ${{ secrets.API_PREFIX }}
22-
NEXT_PUBLIC_SITE_URL: ${{ secrets.NEXT_PUBLIC_SITE_URL }}
20+
IMAGE_NAME: ghcr.io/team-senifit/web
21+
IMAGE_TAG: ${{ github.sha }}
2322

2423
steps:
2524
- name: Checkout
2625
uses: actions/checkout@v4
2726

28-
- name: Setup Node
29-
uses: actions/setup-node@v4
27+
- name: Log in to GHCR
28+
uses: docker/login-action@v3
3029
with:
31-
node-version: "22.x"
32-
cache: "npm"
33-
34-
- name: Install deps
35-
run: npm ci
36-
37-
- name: Build
38-
run: npm run build
39-
40-
# next는 dependencies에 있어야 함. devDeps는 제거해 산출물 슬림화
41-
- name: Prune devDependencies
42-
run: npm prune --omit=dev
43-
44-
- name: Create deploy package
45-
run: |
46-
set -euo pipefail
47-
mkdir -p deploy
48-
# 실행에 필요한 산출물만 포함 (.next, node_modules, 정적파일, 설정)
49-
cp -r .next node_modules package.json package-lock.json public next.config.* deploy/ 2>/dev/null || true
50-
tar -C deploy -czf release.tgz .
30+
registry: ghcr.io
31+
username: ${{ github.repository_owner }}
32+
password: ${{ secrets.GITHUB_TOKEN }}
5133

52-
- name: Add SSH key
53-
uses: webfactory/ssh-agent@v0.9.0
34+
- name: Build and push image
35+
uses: docker/build-push-action@v6
5436
with:
55-
ssh-private-key: ${{ secrets.EC2_SSH_KEY }}
56-
57-
- name: Known hosts
58-
run: |
59-
set -euo pipefail
60-
mkdir -p ~/.ssh
61-
ssh-keyscan -H "${{ secrets.EC2_HOST }}" >> ~/.ssh/known_hosts
62-
63-
- name: Remote prep (cleanup & prepare)
64-
env:
65-
EC2_USER: ${{ secrets.EC2_USER }}
66-
EC2_HOST: ${{ secrets.EC2_HOST }}
67-
run: |
68-
ssh "$EC2_USER@$EC2_HOST" bash -se <<'PREP'
69-
set -euo pipefail
70-
71-
echo "==== System Health Check ===="
72-
df -h /
73-
free -m
74-
75-
APP_DIR="/srv/senifit-front"
76-
echo "==== Cleanup Old Releases ===="
77-
# 폴더가 없으면 생성
78-
if [ ! -d "$APP_DIR/releases" ]; then
79-
sudo mkdir -p "$APP_DIR/releases" "$APP_DIR/shared"
80-
sudo chown $USER:$USER "$APP_DIR" "$APP_DIR/releases" "$APP_DIR/shared"
81-
fi
82-
83-
cd "$APP_DIR/releases"
84-
85-
# 최신 3개 제외 나머지 리스트업
86-
OLD_RELEASES=$(ls -1t | tail -n +4)
87-
88-
if [ -z "$OLD_RELEASES" ]; then
89-
echo "No old releases to clean up."
90-
else
91-
for rel in $OLD_RELEASES; do
92-
echo "Removing old release: $rel"
93-
# current와 중복 여부 체크 (안전을 위해)
94-
CURR="$(readlink -f "$APP_DIR/current" || true)"
95-
REL_PATH="$(readlink -f "$rel")"
96-
if [ "$REL_PATH" != "$CURR" ]; then
97-
rm -rf "$rel" || echo "Warning: Failed to remove $rel"
98-
else
99-
echo "Skipping current release: $rel"
100-
fi
101-
done
102-
fi
103-
104-
# current가 아닌 릴리즈 내부의 캐시 정리
105-
echo "==== Optimizing Passive Releases ===="
106-
CURR="$(readlink -f "$APP_DIR/current" || true)"
107-
for d in "$APP_DIR"/releases/*; do
108-
[ -d "$d" ] || continue
109-
if [ "$(readlink -f "$d")" != "$CURR" ]; then
110-
echo "Cleaning cache/modules in inactive release: $(basename "$d")"
111-
rm -rf "$d/.next/cache" "$d/node_modules" || true
112-
fi
113-
done
114-
PREP
37+
context: .
38+
file: Dockerfile
39+
push: true
40+
tags: ${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }}
41+
build-args: |
42+
NEXT_PUBLIC_SITE_URL=${{ secrets.NEXT_PUBLIC_SITE_URL }}
43+
NEXT_PUBLIC_API_URL=${{ secrets.NEXT_PUBLIC_API_URL }}
44+
SITE_URL=${{ secrets.NEXT_PUBLIC_SITE_URL }}
45+
46+
- name: Configure AWS credentials
47+
uses: aws-actions/configure-aws-credentials@v4
48+
with:
49+
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
50+
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
51+
aws-region: ${{ secrets.AWS_REGION }}
11552

116-
- name: Remote deploy (stream extract, swap, restart)
53+
- name: Deploy via SSM
11754
env:
118-
EC2_USER: ${{ secrets.EC2_USER }}
119-
EC2_HOST: ${{ secrets.EC2_HOST }}
120-
RELEASE_NAME: ${{ env.RELEASE_NAME }}
121-
SERVICE_USER: ${{ secrets.SERVICE_USER }}
55+
SSM_INSTANCE_ID: ${{ secrets.SSM_INSTANCE_ID }}
56+
IMAGE_TAG: ${{ env.IMAGE_TAG }}
57+
IMAGE_NAME: ${{ env.IMAGE_NAME }}
12258
run: |
12359
set -euo pipefail
124-
APP_DIR="/srv/senifit-front"
125-
REL="${RELEASE_NAME}"
126-
SVC="${SERVICE_USER:-ubuntu}"
127-
128-
cat release.tgz | ssh -o StrictHostKeyChecking=yes "$EC2_USER@$EC2_HOST" \
129-
"set -euo pipefail; APP_DIR='$APP_DIR'; REL='$REL'; \
130-
sudo mkdir -p \"\$APP_DIR/releases/\$REL\"; \
131-
sudo chown \$USER:\$USER \"\$APP_DIR/releases/\$REL\"; \
132-
tar -xzf - -C \"\$APP_DIR/releases/\$REL\""
133-
134-
ssh -o StrictHostKeyChecking=yes "$EC2_USER@$EC2_HOST" \
135-
"set -euo pipefail; APP_DIR='$APP_DIR'; REL='$REL'; SVC='$SVC'; \
136-
echo '==== Finalizing Release ===='; \
137-
ln -sfn \"\$APP_DIR/releases/\$REL\" \"\$APP_DIR/current\"; \
138-
sudo systemctl daemon-reload; \
139-
sudo systemctl restart senifit-front"
140-
141-
- name: Public healthcheck (non-blocking)
142-
continue-on-error: true
143-
run: |
144-
curl -fsS -o /dev/null https://senifit.co.kr || echo "::warning::Public healthcheck failed"
60+
COMMAND_ID=$(aws ssm send-command \
61+
--instance-ids "$SSM_INSTANCE_ID" \
62+
--document-name "AWS-RunShellScript" \
63+
--comment "Deploy web image $IMAGE_NAME:$IMAGE_TAG" \
64+
--parameters 'commands=["cd /srv","export IMAGE_TAG='\"$IMAGE_TAG\"'","docker compose down","docker compose pull","docker compose up -d"]' \
65+
--query "Command.CommandId" \
66+
--output text)
67+
68+
aws ssm wait command-executed \
69+
--command-id "$COMMAND_ID" \
70+
--instance-id "$SSM_INSTANCE_ID"
71+
72+
aws ssm get-command-invocation \
73+
--command-id "$COMMAND_ID" \
74+
--instance-id "$SSM_INSTANCE_ID"

Dockerfile

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ WORKDIR /app
1010
COPY --from=deps /app/node_modules ./node_modules
1111
COPY . .
1212

13+
ARG NEXT_PUBLIC_SITE_URL
14+
ARG NEXT_PUBLIC_API_URL
15+
ARG SITE_URL
16+
ENV NEXT_PUBLIC_SITE_URL=${NEXT_PUBLIC_SITE_URL}
17+
ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
18+
ENV SITE_URL=${SITE_URL}
1319
ENV NEXT_TELEMETRY_DISABLED=1
1420
RUN npm run build
1521
RUN npm prune --omit=dev
@@ -21,6 +27,9 @@ ENV NODE_ENV=production
2127
ENV NEXT_TELEMETRY_DISABLED=1
2228
ENV PORT=3000
2329
ENV HOSTNAME=0.0.0.0
30+
ENV NEXT_PUBLIC_SITE_URL=${NEXT_PUBLIC_SITE_URL}
31+
ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
32+
ENV SITE_URL=${SITE_URL}
2433

2534
COPY --from=builder /app/public ./public
2635
COPY --from=builder /app/.next ./.next
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,5 @@ const config = {
2121
lastmod: new Date().toISOString(),
2222
}),
2323
};
24-
export default config;
24+
25+
module.exports = config;

ops/docker-compose.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ services:
55
image: ghcr.io/team-senifit/web:${IMAGE_TAG:-latest}
66
environment:
77
NODE_ENV: production
8+
NEXT_PUBLIC_SITE_URL: ${NEXT_PUBLIC_SITE_URL:-https://senifit.co.kr}
89
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-https://api.senifit.co.kr:8443}
910
volumes:
1011
- ./logs/nextjs:/app/logs/nextjs

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"dev": "next dev --experimental-https --experimental-https-key ./certs/localhost-key.pem --experimental-https-cert ./certs/localhost.pem",
77
"proxy": "node scripts/dev-proxy.mjs",
88
"dev:https": "concurrently -k -n NEXT,SSL \"next dev -p 3001\" \"npm run proxy\"",
9-
"build": "next build && next-sitemap --config next-sitemap.config.mjs",
9+
"build": "next build && next-sitemap --config next-sitemap.config.js",
1010
"start": "next start",
1111
"typecheck": "tsc --noEmit",
1212
"lint": "next lint --max-warnings=0",

public/sitemap-0.xml

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:mobile="http://www.google.com/schemas/sitemap-mobile/1.0" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1">
3-
<url><loc>https://senifit.co.kr/exercise/start</loc><lastmod>2026-01-09T12:59:31.507Z</lastmod><changefreq>monthly</changefreq><priority>0.7</priority></url>
4-
<url><loc>https://senifit.co.kr/icon1.png</loc><lastmod>2026-01-09T12:59:31.510Z</lastmod><changefreq>monthly</changefreq><priority>0.7</priority></url>
5-
<url><loc>https://senifit.co.kr/apple-icon.png</loc><lastmod>2026-01-09T12:59:31.510Z</lastmod><changefreq>monthly</changefreq><priority>0.7</priority></url>
6-
<url><loc>https://senifit.co.kr</loc><lastmod>2026-01-09T12:59:31.510Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
7-
<url><loc>https://senifit.co.kr/icon0.svg</loc><lastmod>2026-01-09T12:59:31.510Z</lastmod><changefreq>monthly</changefreq><priority>0.7</priority></url>
8-
<url><loc>https://senifit.co.kr/login</loc><lastmod>2026-01-09T12:59:31.510Z</lastmod><changefreq>monthly</changefreq><priority>0.7</priority></url>
9-
<url><loc>https://senifit.co.kr/exercise/customized</loc><lastmod>2026-01-09T12:59:31.510Z</lastmod><changefreq>monthly</changefreq><priority>0.7</priority></url>
10-
<url><loc>https://senifit.co.kr/exercise/check-selected</loc><lastmod>2026-01-09T12:59:31.510Z</lastmod><changefreq>monthly</changefreq><priority>0.7</priority></url>
11-
<url><loc>https://senifit.co.kr/exercise/popular</loc><lastmod>2026-01-09T12:59:31.510Z</lastmod><changefreq>monthly</changefreq><priority>0.7</priority></url>
12-
<url><loc>https://senifit.co.kr/exercise/thematic</loc><lastmod>2026-01-09T12:59:31.510Z</lastmod><changefreq>monthly</changefreq><priority>0.7</priority></url>
13-
<url><loc>https://senifit.co.kr/my-center/members</loc><lastmod>2026-01-09T12:59:31.510Z</lastmod><changefreq>monthly</changefreq><priority>0.7</priority></url>
14-
<url><loc>https://senifit.co.kr/my-center/members/add</loc><lastmod>2026-01-09T12:59:31.510Z</lastmod><changefreq>monthly</changefreq><priority>0.7</priority></url>
15-
<url><loc>https://senifit.co.kr/my-center</loc><lastmod>2026-01-09T12:59:31.510Z</lastmod><changefreq>monthly</changefreq><priority>0.7</priority></url>
16-
<url><loc>https://senifit.co.kr/exercise/members</loc><lastmod>2026-01-09T12:59:31.510Z</lastmod><changefreq>monthly</changefreq><priority>0.7</priority></url>
3+
<url><loc>https://senifit.co.kr/icon0.svg</loc><lastmod>2026-02-03T06:21:32.182Z</lastmod><changefreq>monthly</changefreq><priority>0.7</priority></url>
4+
<url><loc>https://senifit.co.kr/apple-icon.png</loc><lastmod>2026-02-03T06:21:32.182Z</lastmod><changefreq>monthly</changefreq><priority>0.7</priority></url>
5+
<url><loc>https://senifit.co.kr/icon1.png</loc><lastmod>2026-02-03T06:21:32.182Z</lastmod><changefreq>monthly</changefreq><priority>0.7</priority></url>
6+
<url><loc>https://senifit.co.kr/exercise/check-selected</loc><lastmod>2026-02-03T06:21:32.182Z</lastmod><changefreq>monthly</changefreq><priority>0.7</priority></url>
7+
<url><loc>https://senifit.co.kr/exercise/customized</loc><lastmod>2026-02-03T06:21:32.182Z</lastmod><changefreq>monthly</changefreq><priority>0.7</priority></url>
8+
<url><loc>https://senifit.co.kr/exercise/start</loc><lastmod>2026-02-03T06:21:32.182Z</lastmod><changefreq>monthly</changefreq><priority>0.7</priority></url>
9+
<url><loc>https://senifit.co.kr/login</loc><lastmod>2026-02-03T06:21:32.182Z</lastmod><changefreq>monthly</changefreq><priority>0.7</priority></url>
10+
<url><loc>https://senifit.co.kr/my-center/members</loc><lastmod>2026-02-03T06:21:32.182Z</lastmod><changefreq>monthly</changefreq><priority>0.7</priority></url>
11+
<url><loc>https://senifit.co.kr</loc><lastmod>2026-02-03T06:21:32.182Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
12+
<url><loc>https://senifit.co.kr/exercise/thematic</loc><lastmod>2026-02-03T06:21:32.182Z</lastmod><changefreq>monthly</changefreq><priority>0.7</priority></url>
13+
<url><loc>https://senifit.co.kr/exercise/popular</loc><lastmod>2026-02-03T06:21:32.182Z</lastmod><changefreq>monthly</changefreq><priority>0.7</priority></url>
14+
<url><loc>https://senifit.co.kr/my-center/members/add</loc><lastmod>2026-02-03T06:21:32.182Z</lastmod><changefreq>monthly</changefreq><priority>0.7</priority></url>
15+
<url><loc>https://senifit.co.kr/my-center</loc><lastmod>2026-02-03T06:21:32.182Z</lastmod><changefreq>monthly</changefreq><priority>0.7</priority></url>
16+
<url><loc>https://senifit.co.kr/exercise/members</loc><lastmod>2026-02-03T06:21:32.182Z</lastmod><changefreq>monthly</changefreq><priority>0.7</priority></url>
1717
</urlset>

src/apis/createAxiosServer.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,30 @@ import { headers as nextHeaders } from "next/headers";
55
import { AuthError } from "./errors";
66

77
const API_PREFIX = normalizePrefix(process.env.NEXT_PUBLIC_API_BASE ?? "/api");
8-
const SITE_URL = ensureOrigin(
9-
process.env.NEXT_PUBLIC_SITE_URL ?? "https://localhost:3000",
10-
);
8+
const SITE_URL = ensureOrigin(process.env.NEXT_PUBLIC_SITE_URL);
119

1210
const devHttpsAgent =
1311
process.env.NODE_ENV !== "production"
1412
? new https.Agent({ rejectUnauthorized: false })
1513
: undefined;
1614

17-
function ensureOrigin(v: string) {
15+
function ensureOrigin(v?: string) {
16+
if (!v) {
17+
throw new Error(
18+
"Missing NEXT_PUBLIC_SITE_URL. Set a valid origin like https://senifit.co.kr",
19+
);
20+
}
1821
// 'localhost:3000' 처럼 스킴이 빠진 값이 와도 보정
19-
if (!/^https?:\/\//i.test(v)) return `https://${v}`;
20-
return v;
22+
const normalized = /^https?:\/\//i.test(v) ? v : `https://${v}`;
23+
try {
24+
const url = new URL(normalized);
25+
if (!url.hostname) throw new Error("Missing hostname");
26+
return url.origin;
27+
} catch {
28+
throw new Error(
29+
`Invalid NEXT_PUBLIC_SITE_URL: "${v}". Set a valid origin like https://senifit.co.kr`,
30+
);
31+
}
2132
}
2233
function normalizePrefix(p: string) {
2334
return p.startsWith("/") ? p : `/${p}`;

0 commit comments

Comments
 (0)