추천 가젯
- 공유 링크 만들기
- X
- 이메일
- 기타 앱
K-Guess 개발기: React + Vite로 한국어 단어 맞추기 게임 만들기
K-Guess 개발기: React + Vite로 한국어 단어 맞추기 게임 만들기
이번에 K-Guess라는 한국어 단어 맞추기 웹 게임을 만들고 배포했습니다. 서비스 주소는 https://k-guess.vercel.app/ 입니다.
처음에는 간단한 웹 게임 프로젝트로 시작했지만, 개발을 진행하면서 단순히 “게임이 돌아가는 수준”이 아니라 실제 서비스처럼 운영 가능한 형태까지 만들어보고 싶었습니다. 그래서 게임 로직뿐 아니라 SEO, 사이트맵, 애드센스 준비, 정책 페이지, 봇 차단, rate limiting까지 함께 다뤘습니다.
프로젝트 개요
K-Guess는 매일 바뀌는 한국어 단어를 맞히는 웹 게임입니다. 영어권의 워드 게임처럼 직관적으로 즐길 수 있으면서도, 한국어 입력과 자모 구조를 고려한 플레이 경험을 제공하는 것이 목표였습니다.
- 클래식 모드: 6자모 정답 단어를 제한된 횟수 안에 맞히는 기본 모드
- 하드 모드: 여러 길이의 단어를 단계적으로 맞히는 모드
- 유사도 모드: 의미적으로 가까운 단어를 탐색하면서 정답에 접근하는 모드
사용한 기술 스택
- React
- Vite
- TypeScript
- Vercel
프론트엔드는 React + Vite 기반으로 구성했고, 정적 배포는 Vercel을 사용했습니다. 빠른 번들링과 단순한 배포 흐름 덕분에 프로토타입을 서비스 수준으로 키워 나가기 좋았습니다.
1. 라우팅과 기본 구조
앱은 React Router 기반으로 구성했고, 홈과 각 게임 모드, 도움말, 정책 페이지를 분리했습니다. 초반에는 게임 화면 자체에 집중했지만, 이후 검색엔진과 광고 심사를 고려하면서 정적 정보 페이지도 중요해졌습니다.
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
<Route path="/privacy-policy" element={<PrivacyPolicy />} />
<Route path="/play" element={<Play />} />
<Route path="/semantic" element={<SemanticPlay />} />
<Route path="/semantic/ranking" element={<SemanticRanking />} />
<Route path="/help" element={<Help />} />
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</BrowserRouter>
이 구조 덕분에 게임 화면과 정보 페이지를 역할별로 분리할 수 있었고, 이후 SEO나 정책 페이지를 붙이기도 훨씬 수월했습니다.
2. SEO 메타 태그와 구조화 데이터 적용
SPA 프로젝트에서 자주 놓치기 쉬운 부분이 SEO입니다. 단순히 title만 바꾸는 정도로 끝내지 않고, 각 페이지마다 description, canonical, Open Graph, Twitter meta, JSON-LD를 넣는 구조를 만들었습니다.
type SeoProps = {
title: string;
description: string;
pathname?: string;
type?: 'website' | 'article';
noindex?: boolean;
image?: string;
imageAlt?: string;
jsonLd?: Record<string, unknown> | Array<Record<string, unknown>>;
};
특히 검색엔진과 공유 미리보기를 위해 아래 항목들을 신경 썼습니다.
- canonical URL 지정
- Open Graph 이미지 추가
- Twitter large image 카드 적용
- 페이지 성격에 맞는 JSON-LD 삽입
예를 들어 홈 화면에는 단순 WebSite만 넣는 대신, Organization + WebSite + WebApplication 구조를 함께 넣었습니다.
jsonLd={[
{
'@context': 'https://schema.org',
'@type': 'Organization',
name: 'K-Guess',
url: 'https://k-guess.vercel.app',
},
{
'@context': 'https://schema.org',
'@type': 'WebSite',
name: 'K-Guess',
url: 'https://k-guess.vercel.app',
inLanguage: 'ko-KR',
},
{
'@context': 'https://schema.org',
'@type': 'WebApplication',
name: 'K-Guess',
url: 'https://k-guess.vercel.app',
applicationCategory: 'GameApplication',
operatingSystem: 'Web',
},
]}
3. SPA의 SEO 한계를 줄이기 위한 프리렌더
React SPA는 클라이언트에서 메타를 바꾸더라도, 검색엔진이 최초로 받는 HTML에는 라우트별 메타가 비어 있는 경우가 많습니다. 그래서 빌드 후 정적 HTML을 라우트별로 생성하는 프리렌더 스크립트를 추가했습니다.
이 방식은 완전한 SSR까지는 아니지만, 주요 정적 페이지에 대해 검색엔진이 바로 읽을 수 있는 title/description/canonical/JSON-LD를 제공하는 데 충분히 유용했습니다.
const routeConfigs = [
{
route: '/',
title: '한국어 단어 맞추기 게임',
description: '클래식, 하드, 유사도 모드로 매일 새로운 한국어 단어 맞추기 게임을 즐겨보세요.',
robots: 'index, follow',
},
{
route: '/about',
title: '서비스 소개',
description: 'K-Guess의 게임 방식, 제공 모드, 서비스 운영 방향을 확인하세요.',
robots: 'index, follow',
},
{
route: '/play',
title: '게임 플레이',
description: 'K-Guess 게임 플레이 화면입니다.',
robots: 'noindex, nofollow',
},
];
빌드 스크립트도 아래처럼 연결해서, 정적 빌드 후 라우트별 HTML을 자동 생성하도록 했습니다.
{
"scripts": {
"build": "tsc -b && vite build && node scripts/prerender-pages.mjs"
}
}
4. sitemap, robots.txt, 애드센스 준비
검색엔진과 애드센스 심사를 고려해 sitemap과 robots.txt도 정리했습니다. 인덱싱 가치가 낮은 플레이 화면은 sitemap에서 제외했고, 소개/문의/개인정보처리방침/도움말 같은 페이지를 추가했습니다.
User-agent: *
Allow: /
Sitemap: https://k-guess.vercel.app/sitemap.xml
또한 AdSense 연결을 위해 head에 스니펫을 넣고, ads.txt도 루트에 추가했습니다.
<script
async
src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3953240120776124"
crossorigin="anonymous">
</script>
google.com, pub-3953240120776124, DIRECT, f08c47fec0942fa0
그리고 광고 심사에서 신뢰성을 높이기 위해 아래 페이지들도 추가했습니다.
- 서비스 소개
- 문의
- 개인정보처리방침
- 도움말
5. Open Graph 이미지와 파비콘
공유 미리보기 품질도 생각해서 OG 이미지와 파비콘도 직접 구성했습니다. 작은 서비스라도 브라우저 탭 아이콘, 홈 화면 아이콘, SNS 공유 이미지가 정리되어 있으면 훨씬 완성도 있게 보입니다.
특히 OG 이미지는 “나중에 하자” 하고 미루기 쉬운데, 실제로는 클릭률이나 서비스 첫인상에 꽤 큰 영향을 줍니다.
6. 봇 차단과 API rate limit
공개 사이트를 운영하면 생각보다 다양한 봇이 접근합니다. 이번 프로젝트에서는 구글 색인과 애드센스는 정상 동작하도록 유지하면서, 일부 수집 봇과 과도한 API 호출은 막도록 했습니다.
미들웨어에서는 Googlebot, AdsBot-Google, Mediapartners-Google 같은 크롤러는 허용하고, AI 수집 봇이나 스크래퍼 계열 User-Agent는 차단하도록 분기했습니다.
const BLOCKED_BOT_PATTERNS = [
/GPTBot/i,
/ClaudeBot/i,
/PerplexityBot/i,
/CCBot/i,
/AhrefsBot/i,
/SemrushBot/i,
];
API에는 간단한 IP 기반 rate limiting도 적용했습니다.
const rateLimit = applyRateLimit(req, {
scope: 'api:newmantle-ranking',
windowMs: 60 * 1000,
maxRequests: 12,
});
if (!rateLimit.allowed) {
res.status(429).json({ error: 'too many requests' });
return;
}
물론 메모리 기반 rate limit은 완전한 방어 수단은 아니지만, 작은 프로젝트에서는 꽤 실용적인 1차 방어선이 됩니다.
7. 만들면서 느낀 점
이번 프로젝트를 하면서 가장 크게 느낀 점은, “웹 서비스는 기능 구현만으로 끝나지 않는다”는 것이었습니다. 게임 로직을 구현하는 것도 중요하지만, 실제 공개 서비스를 만들려면 검색엔진이 읽을 수 있는 구조, 사용자 신뢰를 줄 수 있는 페이지 구성, 광고 플랫폼 대응, 운영 보호 장치까지 같이 설계해야 했습니다.
작은 프로젝트라고 생각하고 시작했지만, 결과적으로는 프론트엔드 개발, 정적 사이트 운영, SEO, 배포, 보안까지 여러 영역을 한 번에 경험할 수 있는 좋은 실전 프로젝트가 되었습니다.
마무리
K-Guess는 단순한 장난감 프로젝트라기보다, 실제로 공개하고 운영할 수 있는 한국어 웹 게임을 목표로 만든 프로젝트입니다. 아직 개선할 부분은 많지만, “기능 구현 → 배포 → 검색 최적화 → 운영 준비”까지 직접 연결해봤다는 점에서 의미가 컸습니다.
직접 플레이해보고 싶다면 아래 링크에서 확인할 수 있습니다.
앞으로는 콘텐츠 확장, 유입 개선, 통계 분석, 게임 모드 고도화 같은 부분도 계속 다듬어볼 생각입니다.
- 공유 링크 만들기
- X
- 이메일
- 기타 앱

댓글
댓글 쓰기