Community-driven Korean subdomain URL shortener. Type
๋ง๋.๊ฐ๊ฒฉ.krto find the best price for ๋ง๋ (dumplings).
ํ๊ธ ํค์๋๋ก ์ต์ ๊ฐ๋ฅผ ์ฐพ์๋ณด์ธ์. ๋ง๋.๊ฐ๊ฒฉ.kr โ ๋ค์ด๋ฒ ์ผํ ์ต์ ๊ฐ ํ์ด์ง๋ก ๋ฐ๋ก ์ด๋!
์ปค๋ฎค๋ํฐ ํฌํ๋ก ๊ฐ ํค์๋์ ๋ชฉ์ ์ง URL์ด ๊ฒฐ์ ๋ฉ๋๋ค.
๋ธ๋ผ์ฐ์ ์ฃผ์์ฐฝ์ {ํค์๋}.๊ฐ๊ฒฉ.kr์ ์
๋ ฅํ์ธ์.
๋ง๋.๊ฐ๊ฒฉ.kr โ ๋ง๋ ์ต์ ๊ฐ ๊ฒ์
๊ฐ๋ฐฉ.๊ฐ๊ฒฉ.kr โ ๊ฐ๋ฐฉ ์ต์ ๊ฐ ๊ฒ์
iphone.๊ฐ๊ฒฉ.kr โ iPhone ์ต์ ๊ฐ ๊ฒ์
๋๊ตฌ๋ ์ฐธ์ฌํ ์ ์์ต๋๋ค! ์๋ ์ธ ๊ฐ์ง ๋ฐฉ๋ฒ ์ค ํ๋๋ฅผ ์ ํํ์ธ์.
์๋ก์ด ํค์๋-URL ๋งคํ์ ์ ์ํฉ๋๋ค. ์ ๋จ์ด ์ ์ํ๊ธฐ โ
๋ฑ๋ก ๋๊ธฐ ์ค์ธ ํค์๋์ ํฌํํ์ธ์. ์ด๋ฆฐ PR ๋ณด๊ธฐ โ
PR์์ ๐ ๋ฐ์์ ๋ฌ๋ฉด ํฌํ ์๋ฃ! 3๊ฐ ์ด์ ๋ชจ์ด๋ฉด ์๋ ๋ฑ๋ก๋ฉ๋๋ค.
๋ฒ๊ทธ ์์ , ๊ธฐ๋ฅ ๊ฐ์ ๋ฑ ์ฝ๋๋ก ๊ธฐ์ฌํฉ๋๋ค. ๊ธฐ์ฌ ๊ฐ์ด๋ โ
Issue ์ ์ โ ์๋ ๊ฒ์ฆ โ PR ์์ฑ โ ์ปค๋ฎค๋ํฐ ํฌํ โ ์๋ ๋ณํฉ โ KV ๋๊ธฐํ
โโโโโโโ โโโโโโโ โโโโโโโ โโโโโโโโโโโโ โโโโโโโโ โโโโโโโโโโ
โ ์ ์ โโโโโโ ๊ฒ์ฆ โโโโโโ PR โโโโโโ ๐ ํฌํ โโโโโโ ๋ณํฉ โโโโโโ KV ๋ฐ์โ
โโโโโโโ โโโโโโโ โโโโโโโ โโโโโโโโโโโโ โโโโโโโโ โโโโโโโโโโ
Issue ์์ฑ ๋ธ๋ก๋ฆฌ์คํธ ์๋ ์์ฑ 3+ ์ฐฌ์ฑ 24์๊ฐ Cloudflare
๋น์์ด ํํฐ (๋ณ๊ฒฝ 5+) ๋๊ธฐ ํ ์๋ ๋๊ธฐํ
์๋ชจ ์ฐํ ํ์ง
๋๋ฉ์ธ ํ์ดํธ๋ฆฌ์คํธ
Safe Browsing
admin-approved ๋ผ๋ฒจ์ด ์๋ PR์ ํฌํ/๋๊ธฐ ์์ด ์ฆ์ ๋ณํฉ๋ฉ๋๋ค.
Cloudflare (๋ฆฌ๋ค์ด๋ ํธ) + Vercel (์น์ฑ) ํ์ด๋ธ๋ฆฌ๋ ๊ตฌ์กฐ๋ก ์ $0 ์ด์๋ฉ๋๋ค.
์ฌ์ฉ์ โ ๋ง๋.๊ฐ๊ฒฉ.kr
โ
Cloudflare Workers (์์ผ๋์นด๋ DNS)
โ Host ํค๋์์ ์๋ธ๋๋ฉ์ธ ์ถ์ถ
โ Punycode ๋์ฝ๋ฉ (xn--hu1b07h โ ๋ง๋)
โ KV ์กฐํ โ 302 ๋ฆฌ๋ค์ด๋ ํธ
โ (KV ์คํจ ์) GitHub Raw Content ํด๋ฐฑ
โ (ํค์๋ ๋ฏธ๋ฑ๋ก ์) Vercel ์น์ฑ์ผ๋ก ์๋ด
price.kr/
โโโ LICENSE # MIT ๋ผ์ด์ ์ค
โโโ CODE_OF_CONDUCT.md # ํ๋ ๊ฐ๋ น
โโโ CONTRIBUTING.md # ๊ธฐ์ฌ ๊ฐ์ด๋
โโโ SECURITY.md # ๋ณด์ ์ ์ฑ
โโโ CLAUDE.md # Claude Code ์ค์
โโโ workers/ # Cloudflare Workers ๋ฆฌ๋ค์ด๋ ํธ ์์ง
โ โโโ src/ # subdomain.ts, fallback.ts, index.ts
โโโ web/ # Next.js 15 App Router (Vercel)
โ โโโ app/ # ๊ฒ์ UI, [keyword] ํ์ด์ง, ๊ฐ์ธ์ ๋ณด์ฒ๋ฆฌ๋ฐฉ์นจ
โ โโโ components/ # SearchBar (์๋์์ฑ, ์ด์ฑ ๊ฒ์)
โ โโโ lib/ # hangul.ts, keywords.ts
โโโ scripts/ # ๊ณต์ ์ ํธ๋ฆฌํฐ
โ โโโ hangul-path.ts # ์ด์ฑ ์ถ์ถ, ํค์๋โํ์ผ ๊ฒฝ๋ก ๊ณ์ฐ
โ โโโ validate-keyword.ts # ํค์๋ ๊ฒ์ฆ (๋ธ๋ก๋ฆฌ์คํธ, ๋น์์ด, ์๋ชจ ์ฐํ ํ์ง)
โ โโโ sync-kv.ts # JSON โ Cloudflare KV ๋๊ธฐํ
โ โโโ seed-data.ts # TSV โ ํค์๋ JSON ์ผ๊ด ์์ฑ
โ โโโ generate-top1000-tsv.ts # Top 1000 ํค์๋ TSV ์์ฑ
โโโ data/ # ํค์๋ ๋ฐ์ดํฐ (3๋จ๊ณ ๋๋ ํ ๋ฆฌ ๊ตฌ์กฐ)
โ โโโ {์ด์ฑ}/{์ฒซ๊ธ์}/{keyword}.json # ํ๊ธ ํค์๋
โ โโโ _en/{keyword}.json # ์๋ฌธ ํค์๋
โ โโโ _num/{keyword}.json # ์ซ์ ์์ ํค์๋
โ โโโ blocklist.json # ์ํ๋ช
๋ธ๋ก๋ฆฌ์คํธ
โ โโโ whitelist.json # ํ์ฉ ๋๋ฉ์ธ ๋ชฉ๋ก
โ โโโ profanity-blocklist.json # ๋น์์ด ๋ธ๋ก๋ฆฌ์คํธ
โโโ .github/
โ โโโ CODEOWNERS # ์ฝ๋ ๋ฆฌ๋ทฐ ๊ท์น
โ โโโ PULL_REQUEST_TEMPLATE.md # PR ์ฒดํฌ๋ฆฌ์คํธ
โ โโโ ISSUE_TEMPLATE/ # Issue ์์ (์ ์/๋ณ๊ฒฝ/์ญ์ /๋ฒ๊ทธ/๊ธฐ๋ฅ)
โ โโโ workflows/
โ โโโ validate-issue.yml # Issue โ ํค์๋/URL ๊ฒ์ฆ โ ์๋ PR
โ โโโ sync-kv.yml # main push โ KV ๋๊ธฐํ
โ โโโ auto-merge.yml # ์ปค๋ฎค๋ํฐ ํฌํ โ ์๋ ๋ณํฉ
โโโ docs/
โโโ PRD.md # ์ ํ ์๊ตฌ์ฌํญ ์ ์์
โโโ OPERATIONS.md # ์ด์ ๊ฐ์ด๋
โโโ DEV_PROGRESS.md # ๊ฐ๋ฐ ์งํ ์ํฉ
โโโ DEV_LOG.md # ๊ธฐ์ ์ ๊ฒฐ์ ๊ธฐ๋ก
โโโ PLAN-top100-keywords.md # ํค์๋ ํ์ฅ ๊ณํ
โโโ superpowers/ # ์ค๊ณ ๋ฌธ์ ๋ฐ ๊ตฌํ ๊ณํ
| ์์ญ | ๊ธฐ์ |
|---|---|
| ๋ฆฌ๋ค์ด๋ ํธ ์์ง | Cloudflare Workers + KV + Cache API |
| ์น ์ฑ | Next.js 15 App Router (Vercel Free) |
| ์๋ํ | GitHub Actions (Issue ๊ฒ์ฆ, KV ๋๊ธฐํ, ์๋ ๋ณํฉ) |
| ๋ฐ์ดํฐ | GitHub ์ ์ฅ์ (JSON ํ์ผ, 3๋จ๊ณ ๋๋ ํ ๋ฆฌ ๊ตฌ์กฐ) |
| ํ ์คํธ | Vitest 2.x |
| ๋ณด์ | Open redirect ๋ฐฉ์ง, Script injection ๋ฐฉ์ง, Google Safe Browsing |
์ ์ ๊ฐ์ด๋ (ํฌํฌ/์์ฒด ์ด์ ์)
- Node.js 20+
- Cloudflare ๊ณ์ (Workers + KV ๋ฌด๋ฃ ํ๋)
- Vercel ๊ณ์ (์น์ฑ ๋ฐฐํฌ์ฉ, ๋ฌด๋ฃ ํ๋)
- ๋๋ฉ์ธ (
๊ฐ๊ฒฉ.kr/xn--o39aom.kr) โ Cloudflare DNS์ ๋ฑ๋ก
git clone https://github.com/price-kr/price.kr.git
cd price.kr
npm install- Cloudflare ๋์๋ณด๋์์ ๋๋ฉ์ธ ์ถ๊ฐ:
xn--o39aom.kr(๊ฐ๊ฒฉ.kr์ punycode) - ๋๋ฉ์ธ ๋ฑ๋ก๊ธฐ๊ด(์: ๊ฐ๋น์, ํ์ด์ฆ ๋ฑ)์์ ๋ค์์๋ฒ๋ฅผ Cloudflare๊ฐ ์ ๊ณตํ๋ NS๋ก ๋ณ๊ฒฝ
Cloudflare DNS ํญ์์ ๋ค์ ๋ ์ฝ๋๋ฅผ ์ถ๊ฐํฉ๋๋ค:
| ํ์ | ์ด๋ฆ | ๊ฐ | ํ๋ก์ | ์ฉ๋ |
|---|---|---|---|---|
| A | @ (๋ฃจํธ) |
76.76.21.21 |
OFF (DNS only) | bare domain โ Vercel ์น์ฑ |
| CNAME | www |
Vercel ๋์๋ณด๋์์ ํ์ธ | OFF (DNS only) | www โ Vercel ์น์ฑ (bare domain์ผ๋ก ๋ฆฌ๋ค์ด๋ ํธ) |
| A | * (์์ผ๋์นด๋) |
192.0.2.1 |
ON (Proxied) | *.๊ฐ๊ฒฉ.kr โ Cloudflare Workers |
| AAAA | * (์์ผ๋์นด๋) |
100:: |
ON (Proxied) | IPv6 ์์ผ๋์นด๋ โ Cloudflare Workers |
ํต์ฌ ๊ตฌ์กฐ:
๊ฐ๊ฒฉ.kr(bare domain)์ Vercel์ด ์ฒ๋ฆฌํ๊ณ ,๋ง๋.๊ฐ๊ฒฉ.kr๋ฑ ์์ผ๋์นด๋ ์๋ธ๋๋ฉ์ธ์ Cloudflare Workers๊ฐ ์ฒ๋ฆฌํฉ๋๋ค.
- ๋ฃจํธ ๋๋ฉ์ธ (
@): Vercel์ A ๋ ์ฝ๋ IP๋ฅผ ์ฌ์ฉํ๋ฉฐ, Cloudflare ํ๋ก์๋ฅผ ๋๋๋ค (ํ์ ๊ตฌ๋ฆ). Vercel์ด ์ง์ SSL๊ณผ ๋ผ์ฐํ ์ ์ฒ๋ฆฌํฉ๋๋ค. www: Vercel ํ๋ก์ ํธ๋ณ CNAME ๊ฐ(Vercel ๋์๋ณด๋ โ Domains์์ ํ์ธ, ํ์:<hash>.vercel-dns.com)์ผ๋ก ์ฐ๊ฒฐํ๋ฉฐ, ํ๋ก์๋ฅผ ๋๋๋ค. Vercel์ด ์๋์ผ๋ก bare domain์ผ๋ก ๋ฆฌ๋ค์ด๋ ํธํฉ๋๋ค.- ์์ผ๋์นด๋ (
*): dummy IP๋ฅผ ์ฌ์ฉํ๋ฉฐ, Cloudflare ํ๋ก์๋ฅผ ์ผญ๋๋ค (์ฃผํฉ ๊ตฌ๋ฆ). ์ค์ ํธ๋ํฝ์ Workers route(*.xn--o39aom.kr/*)๊ฐ ๊ฐ๋ก์ฑ์ ์ฒ๋ฆฌํฉ๋๋ค.
192.0.2.1์ RFC 5737 ๋ฌธ์์ฉ IP์ด๋ฉฐ, origin ์๋ฒ๊ฐ ํ์ ์๋ Cloudflare Workers ์ค์ ์์ ์ฌ์ฉํ๋ ํ์ค ๋ฐฉ์์ ๋๋ค.100::์ RFC 6666 discard prefix์ ๋๋ค. Cloudflare DNS๋ ๊ตฌ์ฒด์ ์ธ ๋ ์ฝ๋(@,www)๋ฅผ ์์ผ๋์นด๋(*)๋ณด๋ค ์ฐ์ ์ฒ๋ฆฌํฉ๋๋ค.
Cloudflare ๋์๋ณด๋ โ SSL/TLS์์ Full ๋ชจ๋๋ฅผ ์ ํํฉ๋๋ค.
SSL/TLS ๋ชจ๋๋ ํ๋ก์๋(์ฃผํฉ ๊ตฌ๋ฆ) ํธ๋ํฝ์๋ง ์ ์ฉ๋ฉ๋๋ค. bare domain๊ณผ
www๋ DNS only(ํ์ ๊ตฌ๋ฆ)์ด๋ฏ๋ก Vercel์ด SSL ์ธ์ฆ์๋ฅผ ์ง์ ๋ฐ๊ธยท๊ด๋ฆฌํฉ๋๋ค. ์์ผ๋์นด๋ ์๋ธ๋๋ฉ์ธ์ Cloudflare Universal SSL(๋ฌด๋ฃ)์ด ์๋์ผ๋ก edge ์ธ์ฆ์๋ฅผ ์ ๊ณตํฉ๋๋ค.
# Wrangler CLI ์ค์น (์ ์ญ)
npm install -g wrangler
# Cloudflare ๋ก๊ทธ์ธ
wrangler login
# KV ๋ค์์คํ์ด์ค ์์ฑ
wrangler kv namespace create KEYWORDS
wrangler kv namespace create KEYWORDS --preview์์ฑ๋ ID๋ฅผ workers/wrangler.toml์ ๋ฐ์:
[[kv_namespaces]]
binding = "KEYWORDS"
id = "<์์์ ์์ฑ๋ KV namespace ID>"
preview_id = "<์์์ ์์ฑ๋ preview ID>"Workers route ์ค์ (workers/wrangler.toml์ ์ด๋ฏธ ํฌํจ):
routes = [
{ pattern = "*.xn--o39aom.kr/*", zone_name = "xn--o39aom.kr" }
]์ด route๋ ์์ผ๋์นด๋ ์๋ธ๋๋ฉ์ธ์ ๋ชจ๋ ๊ฒฝ๋ก๋ฅผ Workers๊ฐ ์ฒ๋ฆฌํ๋๋ก ํฉ๋๋ค. KV ์กฐํ ์คํจ ์ GitHub Raw Content ํด๋ฐฑ์ ์๋ํ๊ณ , ๊ทธ๋ง์ ๋ ํค์๋๋ฅผ ์ฐพ์ง ๋ชปํ๋ฉด
WEB_APP_ORIGIN์ ํตํด Vercel ์น์ฑ์ ํค์๋ ํ์ด์ง๋ก ๋ฆฌ๋ค์ด๋ ํธ๋ฉ๋๋ค.
(์ ํ) GitHub Raw Content ํด๋ฐฑ์ rate limit์ ํ์ฅํ๋ ค๋ฉด (60 โ 5,000 req/hr):
wrangler secret put GITHUB_TOKEN
# GitHub Personal Access Token ์
๋ ฅ (public repo read ๊ถํ)GitHub repo โ Settings โ Secrets and variables โ Actions์์ ์ค์ :
| Secret | ์ฉ๋ | ํ์ ์ฌ๋ถ |
|---|---|---|
CLOUDFLARE_API_TOKEN |
Wrangler๊ฐ KV์ writeํ๊ธฐ ์ํ Cloudflare API ํ ํฐ | ํ์ |
CF_KV_NAMESPACE_ID |
4๋จ๊ณ์์ ์์ฑํ KV namespace ID | ํ์ |
GOOGLE_SAFE_BROWSING_API_KEY |
Issue ๋ฑ๋ก ์ URL ์์ ์ฑ ๊ฒ์ฆ (๋ฐ๊ธ) | ์ ํ (์์ผ๋ฉด ๊ฒ์ฆ skip) |
Cloudflare API ํ ํฐ ์์ฑ:
- Cloudflare API Tokens ํ์ด์ง
- "Create Token" โ "Edit Cloudflare Workers" ํ ํ๋ฆฟ ์ฌ์ฉ
- ๊ถํ์
Account - Workers KV Storage - Edit์ถ๊ฐ - ์์ฑ๋ ํ ํฐ์
CLOUDFLARE_API_TOKEN์ํฌ๋ฆฟ์ ์ ์ฅ
# Vercel CLI ์ค์น ๋ฐ ํ๋ก์ ํธ ์ฐ๊ฒฐ
npm install -g vercel
cd web
vercel linkVercel ๋์๋ณด๋์์ ์ค์ :
- Framework Preset: Next.js
- Root Directory:
web - Build Command: (๊ธฐ๋ณธ๊ฐ ์ฌ์ฉ)
Vercel ์น์ฑ์ ํ๊ฒฝ ๋ณ์๊ฐ ํ์ ์์ต๋๋ค. ๋ฐ์ดํฐ๋ ๋น๋ ์ ํ์ผ์์คํ ์์ ์ง์ ์ฝ์ต๋๋ค.
- Vercel ๋์๋ณด๋ โ ํ๋ก์ ํธ โ Settings โ Domains
xn--o39aom.kr(๊ฐ๊ฒฉ.kr) ๋๋ฉ์ธ ์ถ๊ฐwww.xn--o39aom.kr๋๋ฉ์ธ ์ถ๊ฐ โ Vercel์ด ๊ธฐ๋ณธ ๋๋ฉ์ธ์ ๋ฌป๋๋ค๋ฉดxn--o39aom.kr(bare domain)์ ์ ํํ์ฌwww๊ฐ bare domain์ผ๋ก ๋ฆฌ๋ค์ด๋ ํธ๋๋๋ก ์ค์ - Vercel์ด ์๋ดํ๋ DNS ์ค์ ์ด 3๋จ๊ณ์์ ์ค์ ํ A ๋ ์ฝ๋(
76.76.21.21) ๋ฐwwwCNAME(Vercel ๋์๋ณด๋์ ํ์๋ ํ๋ก์ ํธ๋ณ ๊ฐ)๊ณผ ์ผ์นํ๋์ง ํ์ธ - SSL ์ธ์ฆ์๊ฐ ์๋ ๋ฐ๊ธ๋ ๋๊น์ง ๋๊ธฐ (๋ณดํต ์ ๋ถ ์ด๋ด)
bare domain(
๊ฐ๊ฒฉ.kr)๊ณผwww.๊ฐ๊ฒฉ.kr์ Vercel์ Next.js ์น์ฑ์ด ์๋ตํ๊ณ , ์๋ธ๋๋ฉ์ธ(๋ง๋.๊ฐ๊ฒฉ.kr)์ผ๋ก ์ ์ํ๋ฉด Cloudflare Workers๊ฐ ๋ฆฌ๋ค์ด๋ ํธ๋ฅผ ์ฒ๋ฆฌํฉ๋๋ค.vercel.json์ ํ์ ์์ต๋๋ค.
# Workers ๋ก์ปฌ ๊ฐ๋ฐ (wrangler dev)
cd workers && npm run dev
# Web ๋ก์ปฌ ๊ฐ๋ฐ (Next.js dev server)
cd web && npm run dev
# ์ ์ฒด ํ
์คํธ
npm test# Workers ๋ฐฐํฌ
cd workers && npm run deploy
# Web์ git push ์ Vercel์์ ์๋ ๋ฐฐํฌ
# KV ๋๊ธฐํ๋ main push ์ GitHub Actions๊ฐ ์๋ ์คํ| ์ฆ์ | ์์ธ | ํด๊ฒฐ |
|---|---|---|
*.๊ฐ๊ฒฉ.kr ์ ์ ์ ์ฐ๊ฒฐ ๊ฑฐ๋ถ |
์์ผ๋์นด๋ DNS ๋ ์ฝ๋์ ํ๋ก์๊ฐ ๊บผ์ ธ ์์ (ํ์ ๊ตฌ๋ฆ) | Cloudflare DNS์์ * ๋ ์ฝ๋๋ฅผ Proxied (์ฃผํฉ ๊ตฌ๋ฆ)๋ก ๋ณ๊ฒฝ |
๊ฐ๊ฒฉ.kr ์ ์ ์ SSL ์ค๋ฅ |
๋ฃจํธ ๋๋ฉ์ธ DNS ํ๋ก์๊ฐ ์ผ์ ธ ์์ (์ฃผํฉ ๊ตฌ๋ฆ) | @ ๋ ์ฝ๋๋ฅผ DNS only (ํ์ ๊ตฌ๋ฆ)๋ก ๋ณ๊ฒฝํ์ฌ Vercel์ด SSL ์ฒ๋ฆฌ |
| ์ค์ ํ ๋๋ฉ์ธ์ด ๋์ํ์ง ์์ | DNS ์ ํ ์ง์ฐ (๋ค์์๋ฒ ๋ณ๊ฒฝ ํ ์ต๋ 48์๊ฐ) | dig xn--o39aom.kr ๋๋ nslookup์ผ๋ก ์ ํ ์ํ ํ์ธ ํ ๋๊ธฐ |
curl๋ก ํ
์คํธ ์ ๋ฆฌ๋ค์ด๋ ํธ ์ ๋จ |
Host ํค๋์ ํ๊ธ ๋์ punycode ์ฌ์ฉ ํ์ | curl -I https://xn--hu1b07h.xn--o39aom.kr (๋ง๋.๊ฐ๊ฒฉ.kr) |
| ์ ํค์๋ ๋ฑ๋ก ํ ๋ฆฌ๋ค์ด๋ ํธ ์ ๋จ | KV ์ ํ ์ง์ฐ (์ต๋ 60์ด) ๋๋ Workers ์ฃ์ง ์บ์ (์ต๋ 1์๊ฐ TTL) | KV ํ์ธ: wrangler kv key get, ์บ์ ๋ฌธ์ ์ Cloudflare ๋์๋ณด๋์์ Purge Cache ์คํ |
์ด ํ๋ก์ ํธ์ ์์ค ์ฝ๋๋ MIT ๋ผ์ด์ ์ค๋ก ๊ณต๊ฐ๋ฉ๋๋ค. ๋จ, ๊ฐ๊ฒฉ.kr ๋๋ฉ์ธ ๋ฐ ์๋น์ค ์ธํ๋ผ(Cloudflare, Vercel ์ค์ ๋ฑ)์ ์์ ๊ถ๊ณผ ์ด์ ๊ถํ์ ๋ผ์ด์ ์ค ๋ฒ์์ ํฌํจ๋์ง ์์ต๋๋ค.