Skip to content

Commit 5fff71d

Browse files
authored
(#7) 프런트 build/runtime 하네스 친화화
* chore: harden frontend build and runtime setup * fix: address GRC-03 review feedback
1 parent 5f6a6dd commit 5fff71d

20 files changed

Lines changed: 272 additions & 66 deletions

.env.example

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Local development defaults
2+
NEXT_PUBLIC_BASE_URL=http://localhost:3000
3+
NEXT_PUBLIC_API_URL=http://localhost:8080
4+
5+
# Production reference
6+
# NEXT_PUBLIC_BASE_URL=https://www.git-ranker.com
7+
# NEXT_PUBLIC_API_URL=https://www.git-ranker.com
8+
9+
# Optional analytics / error tracking
10+
# NEXT_PUBLIC_ANALYTICS_ENDPOINT=
11+
# NEXT_PUBLIC_SENTRY_DSN=
12+
# SENTRY_DSN=

Dockerfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
2222
ENV NEXT_PUBLIC_BASE_URL=${NEXT_PUBLIC_BASE_URL}
2323
ENV NEXT_TELEMETRY_DISABLED=1
2424

25+
RUN test -n "$NEXT_PUBLIC_API_URL" || (echo "NEXT_PUBLIC_API_URL build arg is required" >&2 && exit 1)
26+
RUN test -n "$NEXT_PUBLIC_BASE_URL" || (echo "NEXT_PUBLIC_BASE_URL build arg is required" >&2 && exit 1)
27+
2528
RUN npm run build
2629

2730
# Stage 3: Production runner

README.md

Lines changed: 50 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,64 @@
1-
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
1+
# git-ranker-client
22

3-
## Getting Started
3+
`git-ranker-client`는 Git Ranker의 Next.js 16 프런트엔드다. 랭킹 조회, 사용자 상세, 로그인 진입, SEO 메타데이터와 배지 링크를 제공한다.
44

5-
First, run the development server:
5+
## Requirements
66

7-
```bash
8-
npm run dev
9-
# or
10-
yarn dev
11-
# or
12-
pnpm dev
13-
# or
14-
bun dev
7+
- Node.js 20+
8+
- npm
9+
- `NEXT_PUBLIC_BASE_URL`
10+
- `NEXT_PUBLIC_API_URL`
11+
12+
`NEXT_PUBLIC_BASE_URL``NEXT_PUBLIC_API_URL`는 build와 runtime 모두에서 필수다. 값이 없으면 `npm run build`와 런타임 초기화가 즉시 실패한다.
13+
14+
## Environment
15+
16+
로컬 Next.js 실행은 `.env.local`, Docker Compose 실행은 `.env`를 사용하면 된다. 시작점으로는 [.env.example](.env.example)을 복사한다.
17+
18+
로컬 개발 예시:
19+
20+
```env
21+
NEXT_PUBLIC_BASE_URL=http://localhost:3000
22+
NEXT_PUBLIC_API_URL=http://localhost:8080
1523
```
1624

17-
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
25+
프로덕션 예시:
1826

19-
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
27+
```env
28+
NEXT_PUBLIC_BASE_URL=https://www.git-ranker.com
29+
NEXT_PUBLIC_API_URL=https://www.git-ranker.com
30+
```
2031

21-
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
32+
선택 env:
2233

23-
## Learn More
34+
- `NEXT_PUBLIC_ANALYTICS_ENDPOINT`
35+
- `NEXT_PUBLIC_SENTRY_DSN`
36+
- `SENTRY_DSN`
2437

25-
To learn more about Next.js, take a look at the following resources:
38+
선택 env가 없어도 핵심 기능은 동작한다. 없으면 analytics/web vitals 전송이나 Sentry 수집만 비활성화된다.
2639

27-
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28-
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
40+
## Commands
2941

30-
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
42+
```bash
43+
npm install
44+
npm run dev
45+
npm run lint
46+
npx tsc --noEmit
47+
npm run build
48+
```
49+
50+
## Docker Compose
51+
52+
```bash
53+
cp .env.example .env
54+
docker compose up --build
55+
```
3156

32-
## Deploy on Vercel
57+
`docker-compose.yml``Dockerfile``NEXT_PUBLIC_BASE_URL`, `NEXT_PUBLIC_API_URL`가 비어 있으면 즉시 실패한다.
3358

34-
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
59+
## Build And Runtime Notes
3560

36-
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
61+
- `JetBrains Mono`는 공식 JetBrains Mono release v2.304에서 가져온 로컬 자산을 사용한다.
62+
- 폰트 라이선스는 [src/fonts/JetBrainsMono-OFL.txt](src/fonts/JetBrainsMono-OFL.txt)에 보관한다.
63+
- locale routing과 보안 헤더는 [src/proxy.ts](src/proxy.ts)에서 처리한다.
64+
- public URL 정책은 [src/shared/lib/public-env.ts](src/shared/lib/public-env.ts)에서 단일 기준으로 관리한다.

docker-compose.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ services:
66
dockerfile: Dockerfile
77
args:
88
# Build-time args (NEXT_PUBLIC_* are inlined during build)
9-
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL}
10-
NEXT_PUBLIC_BASE_URL: ${NEXT_PUBLIC_BASE_URL}
9+
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:?NEXT_PUBLIC_API_URL is required}
10+
NEXT_PUBLIC_BASE_URL: ${NEXT_PUBLIC_BASE_URL:?NEXT_PUBLIC_BASE_URL is required}
1111
restart: unless-stopped
1212
ports:
1313
- "3000:3000"
@@ -16,8 +16,8 @@ services:
1616
NODE_ENV: production
1717
TZ: Asia/Seoul
1818
# Runtime vars (for middleware/server-side code)
19-
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL}
20-
NEXT_PUBLIC_BASE_URL: ${NEXT_PUBLIC_BASE_URL}
19+
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:?NEXT_PUBLIC_API_URL is required}
20+
NEXT_PUBLIC_BASE_URL: ${NEXT_PUBLIC_BASE_URL:?NEXT_PUBLIC_BASE_URL is required}
2121
deploy:
2222
resources:
2323
limits:

src/app/layout.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import type {Metadata, Viewport} from "next";
22
import localFont from "next/font/local";
3-
import { JetBrains_Mono } from "next/font/google";
43
import Script from "next/script";
54
import "./globals.css";
65
import QueryProvider from "@/shared/providers/query-provider";
@@ -12,6 +11,7 @@ import { WebVitalsReporter } from "@/shared/components/web-vitals-reporter";
1211
import { cn } from "@/shared/lib/utils";
1312
import { LocaleProvider } from "@/shared/providers/locale-provider";
1413
import { getRequestLocale } from "@/shared/i18n/server-locale";
14+
import { publicBaseUrl } from "@/shared/lib/public-env";
1515

1616
const pretendard = localFont({
1717
src: "../fonts/PretendardVariable.woff2",
@@ -20,13 +20,14 @@ const pretendard = localFont({
2020
variable: "--font-sans",
2121
});
2222

23-
const jetbrainsMono = JetBrains_Mono({
24-
subsets: ["latin"],
23+
const jetbrainsMono = localFont({
24+
src: "../fonts/JetBrainsMonoVariable.ttf",
2525
variable: "--font-mono",
2626
display: "swap",
27+
weight: "100 800",
2728
});
2829

29-
const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL || "https://www.git-ranker.com"
30+
const BASE_URL = publicBaseUrl
3031

3132
export async function generateMetadata(): Promise<Metadata> {
3233
const locale = await getRequestLocale()
@@ -105,7 +106,6 @@ export default async function RootLayout({
105106
{/* Preconnect to external origins for faster resource loading */}
106107
<link rel="preconnect" href="https://avatars.githubusercontent.com" />
107108
<link rel="dns-prefetch" href="https://avatars.githubusercontent.com" />
108-
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
109109
<Script
110110
src="https://www.googletagmanager.com/gtag/js?id=G-QKZNEY525E"
111111
strategy="afterInteractive"

src/app/login/layout.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { Metadata } from "next"
22
import { getRequestLocale } from "@/shared/i18n/server-locale"
3+
import { publicBaseUrl } from "@/shared/lib/public-env"
34

4-
const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL || "https://www.git-ranker.com"
5+
const BASE_URL = publicBaseUrl
56

67
export async function generateMetadata(): Promise<Metadata> {
78
const locale = await getRequestLocale()

src/app/login/page.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { cn } from "@/shared/lib/utils"
99
import { LiveTicker, TickerUpdate } from "@/shared/components/ui/live-ticker"
1010
import { GithubIcon } from "@/shared/components/icons/github-icon"
1111
import { useI18n } from "@/shared/providers/locale-provider"
12+
import { githubOAuthStartUrl } from "@/shared/lib/public-env"
1213

1314
// [Data] Action 필드 제거 (User + Tier)
1415
const MOCK_LIVE_UPDATES: TickerUpdate[] = [
@@ -458,7 +459,7 @@ export default function LoginPage() {
458459
const privacyPolicy = locale === "ko" ? PRIVACY_POLICY_KO : PRIVACY_POLICY_EN
459460

460461
const handleGithubLogin = () => {
461-
window.location.href = `${process.env.NEXT_PUBLIC_API_URL}/oauth2/authorization/github`
462+
window.location.href = githubOAuthStartUrl
462463
}
463464

464465
const containerVariants = {

src/app/page.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import type { Metadata } from "next"
22
import { HeroSection } from "@/features/home/components/hero-section"
33
import { GithubIcon } from "@/shared/components/icons/github-icon"
44
import { getRequestLocale } from "@/shared/i18n/server-locale"
5+
import { publicBaseUrl } from "@/shared/lib/public-env"
56

6-
const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL || "https://www.git-ranker.com"
7+
const BASE_URL = publicBaseUrl
78

89
export async function generateMetadata(): Promise<Metadata> {
910
const locale = await getRequestLocale()

src/app/ranking/layout.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import type { Metadata } from "next"
22
import { getRequestLocale } from "@/shared/i18n/server-locale"
3+
import { publicBaseUrl } from "@/shared/lib/public-env"
34

4-
const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL || "https://www.git-ranker.com"
5+
const BASE_URL = publicBaseUrl
56

67
export async function generateMetadata(): Promise<Metadata> {
78
const locale = await getRequestLocale()

src/app/robots.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { MetadataRoute } from "next";
2+
import { publicBaseUrl } from "@/shared/lib/public-env";
23

3-
const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL || "https://www.git-ranker.com";
4+
const BASE_URL = publicBaseUrl;
45

56
export default function robots(): MetadataRoute.Robots {
67
return {

0 commit comments

Comments
 (0)