TRENDSHIFT DEEP DIVE · GITHUB 9.3K STARS

OpenStock — 무료로 만든
오픈소스 블룸버그

월 24달러짜리 주식 정보 플랫폼을 Next.js 15 + MongoDB + Finnhub + TradingView 위젯으로 처음부터 다시 만든 프로젝트. AGPL-3.0으로 풀어놓고, AI 모델 라우팅, Inngest 크론, Better Auth, Docker Compose까지 한 레포에 다 들어있어 "현대 풀스택" 학습 표본으로 적격.

1프로젝트 한 줄 요약

이 레포가 뭐길래 별 9.3천 개를 받았나.

핵심 메시지

"비싼 블룸버그 터미널 자리에
Next.js 한 그릇으로 차린 무료 식탁."

OpenStock은 인도 인디 개발자 그룹 Open Dev Society가 만든 오픈소스 주식 정보 플랫폼이다. 실시간 시세, 종목 워치리스트, 가격 알림, 회사 프로파일, 시장 뉴스를 한 페이지에 묶고, 사용자 성향에 맞춘 AI 환영 메일과 주간 시장 요약을 자동 발송한다.

중요한 건 "되긴 된다" 수준이 아니라 Next.js 15 App Router · React 19 · MongoDB · Better Auth · Inngest · Finnhub · TradingView 위젯 · Docker Compose까지 2026년 풀스택 트렌드의 거의 모든 조각을 다 끼워 맞춘 참고 표본이라는 점이다.

용어
Bloomberg Terminal (블룸버그 터미널)
월 약 2,500달러(약 350만 원)에 팔리는 전문 트레이더용 단말기. 실시간 시세·뉴스·차트·메신저가 하나에 묶여 있다. 일반인은 못 쓴다. OpenStock은 이걸 무료로 흉내내는 게 목표.

2왜 주목받는가

9.3k 별, 1.2k 포크는 우연이 아니다.

OpenStock이 TrendShift에 오른 이유는 세 가지로 정리된다.

이유 1

"비싼 도구를 무료로" 라는 명확한 명분

레포 README 상단 Manifesto(선언문)은 거의 정치적이다. "지식은 페이월 뒤에 숨겨져 있고, 도구는 구독에 잠겨 있다"로 시작해서 "우리는 절대 지식을 잠그지 않겠다, 절대 접근을 유료화하지 않겠다"로 끝난다. 라이선스도 AGPL-3.0이라 누가 가져다 SaaS로 팔아도 소스를 다시 풀어야 한다.

이유 2

JavaScript Mastery 튜토리얼 기반의 진입 장벽 낮음

README 맨 아래 보면 솔직하게 적혀 있다 — Adrian Hajdin(JavaScript Mastery 채널)의 "Stock Market App" 유튜브 튜토리얼을 토대로 시작한 프로젝트라고. 그래서 영상으로 따라 배우면서 실제 9k 별짜리 레포 코드를 그대로 읽을 수 있다는 학습 콤보가 생긴다.

"Huge thanks to Adrian Hajdin (JavaScript Mastery) — his excellent Stock Market App tutorial was instrumental in building OpenStock for the open-source community under the Open Dev Society." — README
이유 3

2026년 최신 스택의 "쇼케이스" 역할

Next.js 15 + Turbopack, React 19, Tailwind v4(설정 파일 없는 PostCSS 방식), Better Auth, Inngest, shadcn/ui, Radix Primitives까지 지금 채용 공고에 가장 자주 등장하는 단어들이 한 레포에서 같이 굴러간다. 회사 면접용 포트폴리오 베이스로 포크하기 좋다.

비유

OpenStock은 마치 "중고나라에서 본 풀옵션 자취방" 같다. 비싼 빌트인 가전이 다 들어 있는데(=Inngest, Better Auth), 보증금은 0원(=AGPL-3.0)이고, 집주인이 "맘에 들면 너도 똑같이 풀옵션으로 누군가에게 빌려주라"고 말한다(=Copyleft).

경쟁 제품 대비 장점

VS BLOOMBERG / REFINITIV
월 0원 vs 월 350만 원

전문가용 단말기와 기능 격차는 분명하지만, 학습/취미/소액 투자자 입장에서 "필요한 80%"는 충족. Finnhub 무료 티어로 시작 가능.

VS YAHOO FINANCE
소스 코드가 다 열려있다

Yahoo Finance는 광고로 도배되고 검색 UX가 느린데, OpenStock은 ⌘+K(커맨드 팔레트)로 0.2초 만에 종목 검색. 더 중요한 건 자기 입맛대로 포크해서 고칠 수 있다는 자유.

VS TRADINGVIEW
차트는 어차피 TradingView 위젯

OpenStock은 정직하다 — 차트 부분은 TradingView 임베드 위젯을 그대로 쓴다. 무료 차트 라이브러리는 결국 TradingView가 표준이라는 걸 인정하고, 그 위에 계정 시스템 · 워치리스트 · 알림이라는 부가가치만 얹는다.

3기술 스택 전체 지도

package.json 기준 — README 그대로가 아니라 실제 의존성 분석.

레포 언어 비중은 TypeScript 90.4% · JavaScript 5.2% · CSS 4.2%. 거의 완전한 TS 프로젝트라고 봐도 된다. Python·Go·Rust 같은 백엔드는 0%. 모든 서버 로직은 Next.js의 서버 액션API 라우트로 처리된다.

1) 프론트엔드 (UI / 클라이언트)

프레임워크
Next.js 15.5.7 + Turbopack
React 19를 감싸는 풀스택 프레임워크. App Router(폴더 기반 라우팅) 사용. dev·build 둘 다 Turbopack(Rust 기반 차세대 번들러)으로 돌린다. Webpack 시대 대비 빌드가 5~10배 빠르다.
UI 컴포넌트
shadcn/ui + Radix UI Primitives
shadcn/ui는 "라이브러리"가 아니라 "npm에 없는 컴포넌트 모음"이다. CLI로 코드를 내 레포에 복붙해 넣는 방식. 접근성(키보드/스크린리더) 처리는 Radix(headless 컴포넌트 모음)가 담당한다. OpenStock은 components/ui/ 폴더에 button, dialog, command, dropdown-menu, popover, select, avatar 등을 내장.
스타일
Tailwind CSS v4 (@tailwindcss/postcss)
Tailwind v4는 tailwind.config.js가 더 이상 필요 없다 — CSS 파일 안에 @theme 디렉티브로 설정한다. OpenStock 레포에 tailwind.config가 안 보이는 이유.
기타 클라이언트
cmdk · next-themes · react-hook-form · sonner · lucide-react
cmdk는 ⌘+K 커맨드 팔레트. next-themes는 다크모드 토글. react-hook-form은 비제어 폼(성능 좋음). sonner는 토스트 알림. lucide-react는 아이콘.

2) 백엔드 (서버 / 데이터)

인증
Better Auth 1.3.25
2024~2025년에 급부상한 NextAuth.js 대안. TypeScript 우선, 플러그인 구조, DB 어댑터(MongoDB/PostgreSQL/MySQL) 분리. OpenStock은 mongodbAdapter + emailAndPassword + nextCookies 플러그인 조합. 비밀번호 길이 8~128자, 자동 로그인, 이메일 인증 비활성.
데이터베이스
MongoDB 7 + Mongoose 8.19
NoSQL 문서 DB. OpenStock은 컬렉션 user, watchlist, alert(코드 기준)을 사용. Mongoose ODM으로 스키마 정의. 워치리스트는 {userId, symbol} 복합 유니크 인덱스로 종목 중복을 차단한다.
백그라운드 잡 / 큐
Inngest 3.47
서버리스 환경에서 크론·이벤트·재시도·단계별 워크플로우(step functions)를 처리하는 SaaS. AWS SQS+Lambda+EventBridge를 한 SDK로 묶은 셈. OpenStock은 회원가입 환영 메일, 주간 시장 뉴스 브로드캐스트, 5분마다 가격 알림 체크, 매일 비활성 사용자 재참여 메일 — 4가지 함수를 정의.
외부 시장 데이터
Finnhub API
무료 티어가 있는 미국 시장 데이터 제공자(finnhub.io/api/v1). 종목 검색, 회사 프로파일, 시세, 뉴스. 무료는 보통 15분 지연 + 분당 60콜 제한. NEXT_PUBLIC_FINNHUB_API_KEY로 브라우저에서도 호출 가능(=공개됨).
AI 인텔리전스
Google Gemini 2.5 Flash Lite + MiniMax / Siray.ai (멀티 프로바이더 폴백)
환영 메일·뉴스 요약 텍스트 생성. 핵심은 단일 실패 지점이 없다는 것callAIProviderWithFallback() 함수가 Gemini 실패 시 MiniMax(또는 Siray.ai)로 라우팅한다. AGPL 오픈소스가 24/7 가용성을 흉내내는 방법.
이메일
Nodemailer (Gmail) + Kit / ConvertKit
트랜잭션(환영, 비번 리셋)은 Nodemailer + Gmail SMTP. 마케팅 브로드캐스트(주간 뉴스)는 Kit(구 ConvertKit) API의 POST /v3/broadcasts. 2FA 켰으면 Gmail App Password 필수.

3) 인프라 / 운영

컨테이너
Docker + Docker Compose
Dockerfile은 node:20-alpine 기반의 단순한 단일 스테이지 빌드(멀티 스테이지 최적화는 안 됨 — 학습용으로 일부러 단순하게). docker-compose.yml에 openstock + mongodb:7 서비스, 영속 볼륨 mongo-data, healthcheck까지 들어있다.
배포
Vercel
README에 openstock-ods.vercel.app 라이브 데모. Inngest는 Vercel과 잘 어울리는 SaaS — 서버리스 함수에서 큐/크론을 처리하기 까다로운 문제를 외부에 위임.
CI / 코드 리뷰
CodeRabbit (AI Code Review)
README 토픽에 coderabbit이 들어 있고 컨트리뷰터 목록에 @coderabbitai[bot]이 있다. PR마다 AI가 자동 리뷰 코멘트를 단다.
테스트
Vitest 4
package.jsonvitest 4.1 devDependency로 들어 있고, __tests__/ 디렉토리에 5개 테스트 파일(adanos.actions.test.ts, ai-provider.integration.test.ts, ai-provider.test.ts, reset-password-email.test.ts, utils.test.ts)이 존재하나 커버리지는 제한적. "테스트 인프라는 깔려 있지만 채워야 할 자리"가 많다는 뜻 = 컨트리뷰션 기회.

4아키텍처 심화 분석

데이터 흐름과 핵심 설계 패턴.

OpenStock의 백엔드는 단일 Next.js 프로세스이지만, "동기 요청""비동기 잡"의 경계가 깔끔하게 나뉘어 있다. 사용자 클릭에 반응하는 건 Next.js가, 시간이 걸리는 건 Inngest가 받는 구조.

전체 시스템 구조도

┌────────────────────────────────────────────────────────────────────┐
│                     브라우저 (React 19 + cmdk)                      │
│   ⌘+K 검색  ·  워치리스트  ·  TradingView 위젯 (iframe)             │
└──────────────┬─────────────────────────────────┬───────────────────┘
               │ HTTPS                           │ 위젯 embed
               ▼                                 ▼
┌────────────────────────────────────┐   ┌──────────────────────┐
│  Next.js 15 (App Router @ Vercel)  │   │  s.tradingview.com   │
│  ┌──────────────────────────────┐  │   │  (차트/heatmap/뉴스) │
│  │ middleware/index.ts (세션 가드)│  │   └──────────────────────┘
│  ├──────────────────────────────┤  │
│  │ (auth)/sign-in, sign-up      │  │
│  │ (root)/page, stocks/[symbol] │  │
│  │ api/inngest/route.ts (웹훅)  │  │
│  └──────────┬───────────────────┘  │
│             │ Server Action 호출   │
│  ┌──────────▼───────────────────┐  │       ┌─────────────────┐
│  │ lib/actions/                 │  │       │   Finnhub API   │
│  │  ├ finnhub.actions.ts ───────┼──┼──────▶│  /quote /search │
│  │  ├ watchlist.actions.ts ─┐   │  │       │  /company-news  │
│  │  ├ user.actions.ts       │   │  │       └─────────────────┘
│  │  └ auth (Better Auth) ─┐ │   │  │
│  └────────────────────────┼─┼───┘  │
│                           │ │      │       ┌─────────────────┐
│             ┌─────────────▼─▼───┐  │       │   MongoDB 7     │
│             │ Mongoose / Driver │──┼──────▶│ users           │
│             └───────────────────┘  │       │ watchlist       │
│                                    │       │ alerts          │
└────────────┬───────────────────────┘       └─────────────────┘
             │ event: app/user.created
             │ event: app/send.weekly.news
             ▼
┌─────────────────────────────────────────────────────────────┐
│             Inngest Cloud (이벤트 + 크론 + 재시도)           │
│  ┌──────────────────────────────────────────────────────┐   │
│  │  sign-up-email     ◀── event app/user.created        │   │
│  │  weekly-news       ◀── cron 0 9 * * 1                │   │
│  │  check-stock-alerts◀── cron */5 * * * *              │   │
│  │  check-inactive    ◀── cron 0 10 * * *               │   │
│  └──────┬────────────────────────────┬──────────────────┘   │
│         │ step.run                   │ step.run             │
│         ▼                            ▼                      │
│  ┌──────────────┐            ┌───────────────┐              │
│  │ AI provider  │            │  Nodemailer / │              │
│  │  Gemini ─┐   │            │  Kit Broadcast│              │
│  │          ▼   │            └───────┬───────┘              │
│  │  Siray.ai 폴백│                   │ 발송                 │
│  └──────────────┘                    ▼                      │
│                              ┌───────────────┐              │
│                              │ 사용자 메일함 │              │
│                              └───────────────┘              │
└─────────────────────────────────────────────────────────────┘

핵심 설계 패턴 5가지

패턴 1

App Router의 라우트 그룹 — (auth) vs (root)

괄호 폴더((auth), (root))는 URL 경로에 영향 없이 레이아웃만 분리한다. 로그인/회원가입 페이지는 (auth)/layout.tsx로 사이드바·헤더 없는 풀스크린 레이아웃, 본 앱 페이지는 (root)/layout.tsx로 헤더·푸터 포함. URL은 그냥 /sign-in, /stocks/AAPL이다.

패턴 2

middleware.ts로 세션 가드 일괄 처리

각 페이지마다 "로그인했나?" 체크 코드를 안 짜고, 루트 middleware.ts에서 한 번에 처리. sign-in, sign-up, 정적 자산만 공개로 두고 나머지는 세션 토큰을 검사한다. 횡단 관심사(cross-cutting concern)를 한 곳에 모으는 전형적 패턴.

패턴 3

Server Actions 우선, API 라우트는 외부 웹훅용

Next.js 14부터 도입된 Server Actions"use server" 한 줄로 클라이언트에서 호출 가능한 서버 함수. OpenStock은 lib/actions/ 전부 서버 액션. 진짜 app/api/ 라우트는 inngest 웹훅 받는 용도 하나(api/inngest/route.ts)뿐이다.

교훈: REST API 만들기 전에 "이거 외부에서 호출하나?" 자문. 같은 Next.js 앱 안에서만 쓰면 Server Action이 훨씬 짧고 타입 안전하다.
패턴 4

Inngest step.run — 단계별 재시도 가능한 워크플로우

회원가입 환영 메일 함수를 보면 step.run('generate-welcome-intro', ...)step.run('send-welcome-email', ...)로 명시적으로 단계가 나뉘어 있다. AI 호출이 실패하면 거기서만 재시도, 메일 발송이 실패하면 거기서만 재시도. "전체 함수 처음부터 다시"가 아니라 "실패한 단계만 다시".

패턴 5

멀티 프로바이더 AI 폴백 (callAIProviderWithFallback)

Gemini 무료 티어는 분당 요청 한도가 있어 트래픽이 몰리면 429를 뱉는다. OpenStock은 try/catch로 잡아서 Siray.ai로 자동 재시도. 둘 다 실패하면 하드코딩된 기본 문구로 폴백. 정전 시 비상 발전기 패턴.

5디렉토리 구조 해부

레포에 처음 떨어졌을 때 길 찾는 법.

OpenStock/
├── app/                              ← Next.js App Router 진입점
│   ├── (auth)/                       ← 풀스크린 인증 페이지 그룹
│   │   ├── layout.tsx
│   │   ├── sign-in/page.tsx
│   │   └── sign-up/page.tsx
│   ├── (root)/                       ← 본 앱 페이지 그룹
│   │   ├── layout.tsx                ← 헤더/푸터 포함
│   │   ├── page.tsx                  ← 홈 (대시보드)
│   │   ├── help/page.tsx
│   │   └── stocks/[symbol]/page.tsx  ← 동적 라우트 (AAPL, TSLA…)
│   ├── api/inngest/route.ts          ← Inngest가 호출하는 웹훅 1개
│   ├── globals.css
│   └── layout.tsx                    ← 최상위 레이아웃
│
├── components/                       ← React 컴포넌트
│   ├── ui/                           ← shadcn 프리미티브
│   │   ├── button.tsx, dialog.tsx, command.tsx
│   │   ├── input.tsx, select.tsx, dropdown-menu.tsx
│   │   └── avatar.tsx, popover.tsx, ...
│   ├── forms/                        ← 도메인 폼 컴포넌트
│   │   ├── InputField.tsx
│   │   ├── SelectField.tsx
│   │   ├── CountrySelectField.tsx
│   │   └── FooterLink.tsx
│   ├── Header.tsx
│   ├── Footer.tsx
│   ├── SearchCommand.tsx             ← ⌘+K 검색 팔레트
│   └── WatchlistButton.tsx           ← 별표 토글
│
├── database/
│   ├── models/
│   │   └── watchlist.model.ts        ← Mongoose 스키마
│   └── mongoose.ts                   ← 연결 캐싱 (Next.js HMR 대응)
│
├── lib/
│   ├── actions/                      ← Server Actions
│   │   ├── auth.actions.ts
│   │   ├── finnhub.actions.ts        ← 시세/검색/뉴스 fetch
│   │   ├── user.actions.ts
│   │   └── watchlist.actions.ts      ← add/remove/list
│   ├── better-auth/auth.ts           ← Better Auth 인스턴스
│   ├── inngest/
│   │   ├── client.ts
│   │   ├── functions.ts              ← 4개 함수: 환영/주간뉴스/알림/재참여
│   │   └── prompts.ts                ← AI 프롬프트 템플릿
│   ├── nodemailer/                   ← SMTP 트랜스포터 + 메일 템플릿
│   ├── ai-provider.ts                ← Gemini→Siray 폴백 로직 (추정 위치)
│   ├── kit.ts                        ← Kit/ConvertKit API 래퍼
│   ├── constants.ts
│   └── utils.ts
│
├── scripts/
│   └── test-db.mjs                   ← pnpm test:db 진입점
│
├── types/global.d.ts                 ← 글로벌 타입 선언
│
├── middleware/
│   └── index.ts                      ← 세션 가드 + 라우트 보호
├── next.config.ts                    ← i.ibb.co 이미지 도메인 허용
├── postcss.config.mjs                ← Tailwind v4 PostCSS
├── components.json                   ← shadcn CLI 설정
├── docker-compose.yml                ← openstock + mongodb
├── Dockerfile                        ← node:20-alpine
├── API_DOCS.md                       ← 아키텍처 다이어그램 (Mermaid)
└── package.json

"어디부터 봐야 하나" 가이드

읽기 순서 추천

1) middleware.ts — 어떤 경로가 보호되는지부터. 2) app/(root)/page.tsx — 메인 대시보드. 3) lib/actions/finnhub.actions.ts — 외부 API를 어떻게 감싸는지. 4) lib/inngest/functions.ts — 비동기 워크플로우의 모범 사례. 5) lib/better-auth/auth.ts — 인증 설정.

6학습 포인트

이 레포에서 진짜로 배울 수 있는 것들.

Next.js 15 App Router

배울 것

라우트 그룹 + 동적 라우트 + 서버 액션 패턴

"폴더 = 경로"라는 단순한 원칙 위에 (grouping), [dynamic], page.tsx, layout.tsx의 의미를 익힐 수 있다. 그리고 거의 모든 서버 로직이 Server Action으로 짜여 있어 — "언제 API 라우트, 언제 Server Action을 쓰나"의 실전 감각이 잡힌다.

실습 아이디어

같은 페이지를 한 번은 Server Action으로, 한 번은 app/api/... + fetch()로 짜보고 코드 라인 수와 타입 안정성을 비교.

Better Auth (NextAuth 대안)

배울 것

플러그인 기반 인증 + DB 어댑터 분리

NextAuth는 설정이 복잡한 걸로 악명 높은데, Better Auth는 객체 하나에 옵션 평탄하게 적는 스타일. nextCookies() 플러그인 한 줄로 쿠키 처리 자동화. sendResetPassword 콜백으로 자기 이메일 발송 로직 연결.

실습 아이디어

Better Auth에 Google OAuth 플러그인을 추가해서 소셜 로그인 붙이기. 또는 어댑터를 MongoDB에서 PostgreSQL로 바꿔보기.

Inngest 서버리스 워크플로우

배울 것

크론 · 이벤트 · 단계별 재시도

Vercel 같은 서버리스에 큐를 붙이는 건 골치 아픈데, Inngest는 createFunction + step.run 두 줄로 끝난다. 5분마다 가격을 체크하고 알람을 발사하는 시스템이 50줄짜리 함수 하나로 완성.

실습 아이디어

"매일 자정에 어제 가격과 비교해서 5% 이상 움직인 종목 메일 발송" 함수 새로 추가. step.sleepUntil로 특정 시간까지 대기시키는 기능도 시도.

Finnhub API 통합

배울 것

외부 API를 Server Action으로 감싸는 법

lib/actions/finnhub.actions.ts 패턴: (1) 환경 변수로 키 관리, (2) 함수마다 "use server" 명시, (3) 에러 핸들링과 폴백 값 반환, (4) 검색 디바운싱은 클라이언트(cmdk)에서.

실습 아이디어

Finnhub 대신 Alpha VantageYahoo Finance unofficial API로 교체. 인터페이스만 같으면 갈아끼울 수 있게 추상화 계층 만들기.

TradingView 임베드 위젯

배울 것

외부 위젯을 React 컴포넌트로 감싸기

TradingView는 <script> 태그를 동적으로 주입하는 방식의 위젯을 제공. React에선 useEffect에서 스크립트를 만들고, 언마운트 시 정리하는 패턴이 필요. "외부 JS를 React 라이프사이클 안에 가두는 법"의 표본.

Docker Compose로 풀스택 개발 환경

배울 것

"git clone → docker compose up" 한 줄 데모

depends_on, healthcheck, volumes, env_file의 실용 예시. MongoDB의 authSource=admin 같은 꿀팁이 docker-compose.yml에 다 들어 있다.

함정 주의
Dockerfile에 멀티스테이지 빌드가 없다

현재 Dockerfile은 단일 스테이지 — node_modules까지 그대로 이미지에 들어간다. 실제 프로덕션에선 builder 스테이지 + runner 스테이지로 분리해서 standalone 빌드만 복사해야 이미지 크기가 1.5GB → 200MB로 줄어든다. 컨트리뷰션 기회다.

7하드웨어/시스템 요구사항

내 노트북에서 돌아가나?

OpenStock은 웹 앱이라 하드웨어 요구는 가볍다. 하지만 "외부 SaaS 의존성"이 많아 API 키 셋업이 진입 장벽이 된다.

개발 머신
Node.js 20 LTS 이상 · RAM 4GB 이상
Next.js 15 + Turbopack은 Node 18.18 이상 필요. node --version이 v20.x면 안전. Mac M1/M2/Intel·Windows·Linux 모두 OK. RAM은 dev 서버 + MongoDB 컨테이너 합쳐 1.5GB 정도 잡아먹는다.
디스크
레포 + node_modules + Docker 이미지 ≈ 2.5GB
node_modules가 약 600MB, MongoDB 7 이미지 약 800MB, openstock 빌드 이미지 약 1GB. SSD 권장.

필요한 외부 계정 / 키

필수

1. MongoDB 연결 문자열

옵션 A: 로컬 Docker (mongodb://root:example@mongodb:27017/openstock?authSource=admin). 옵션 B: MongoDB Atlas 무료 티어 (512MB까지 무료).

필수

2. Finnhub API 키

finnhub.io 가입 → 무료 키 발급. 분당 60 콜 제한. 실시간 시세는 유료지만 학습용은 무료 충분.

필수

3. Gmail 앱 비밀번호 (Nodemailer)

2단계 인증 켰으면 일반 비밀번호 안 됨 — Google 계정 설정에서 "앱 비밀번호" 발급. 16자리 코드.

선택

4. Google Gemini API · Inngest Signing Key · Kit API

AI 환영 메일 끄고 싶으면 Gemini 키는 안 받아도 됨(폴백 문구가 나간다). Inngest Signing Key는 Vercel 배포 시 필수. Kit은 마케팅 브로드캐스트 안 쓰면 생략 가능.

최소 시작 옵션
Mongo만 있으면 일단 돌아간다

Finnhub 키 없으면 검색은 안 되지만 회원가입/로그인/UI 렌더링은 확인 가능. Inngest는 npx inngest-cli@latest dev로 로컬 더미 서버를 띄울 수 있다.

8직접 해볼 수 있는 실습 과제

난이도별 5개 — 쉬운 것부터 차근차근.

난이도 ★☆☆☆☆ · 30분

과제 1. 한국어 회사명 검색 지원

Finnhub은 영어 회사명만 검색됨. lib/actions/finnhub.actions.tssearchStocks한글 → 영문 매핑 테이블 추가하기. 예: "삼성전자" → "Samsung", "애플" → "Apple". lib/constants.tsKOREAN_TO_ENGLISH_MAP 객체 만들고 검색 전에 변환.

난이도 ★★☆☆☆ · 1~2시간

과제 2. 다크/라이트 테마 토글 + 컬러 변경

OpenStock은 다크 테마 디폴트. next-themes가 이미 깔려 있으니 헤더에 해/달 아이콘 토글 추가. Tailwind v4의 @theme 디렉티브로 라이트 모드 색상도 정의. lucide-react의 Sun, Moon 아이콘 사용.

난이도 ★★★☆☆ · 반나절

과제 3. 워치리스트에 메모 필드 추가

database/models/watchlist.model.tsnote: { type: String, maxlength: 280 } 추가. 워치리스트 UI에 트위터 길이 메모창 + 저장 버튼 부착. Server Action updateWatchlistNote(symbol, note) 새로 추가. "왜 이 종목을 담았는지" 미래의 나에게 남기는 기능.

난이도 ★★★★☆ · 주말 1회

과제 4. 새 Inngest 함수 — 주간 워치리스트 성과 리포트

매주 일요일 오전 9시(cron: '0 9 * * 0')에 사용자의 워치리스트 종목들의 1주일 등락률을 계산해 메일 발송. step 분리: ① 모든 사용자 조회 → ② 각 사용자의 워치리스트 가져오기 → ③ Finnhub /quote로 1주일 전/지금 가격 비교 → ④ AI에 요약 부탁 → ⑤ Nodemailer 발송. 실패 단계만 재시도의 진가 체험.

난이도 ★★★★★ · 1~2주

과제 5. WebSocket 실시간 시세 스트리밍

현재 OpenStock은 페이지 로드 시점의 시세만 보여준다(풀(pull) 방식). Finnhub는 WebSocket으로 푸시(push) 시세도 제공. Next.js 15는 WS 직접 지원이 약하니 별도 Node WS 게이트웨이 추가 → 클라이언트에서 useWebSocket 훅으로 구독 → Redux/Zustand로 상태 관리. 실시간 차트 깜빡임까지 구현하면 거의 미니 블룸버그.

주의
"무료 데이터"의 한계를 정직하게 인정

Finnhub 무료는 15분 지연 시세 + 분당 60콜. 진짜 트레이딩 용도가 아니라 학습/포트폴리오 검토용. 실거래 알림 만들 거면 유료 데이터 + 정확도 검증 + Disclaimer 필수.

9관련 기술 심화 학습 로드맵

8주 코스로 OpenStock 풀스택을 내것으로.

WEEK 1~2

Next.js 15 App Router 정복

읽기: Next.js 공식 docs/app 전체 통독, "Learn Next.js" 코스. 실습: 빈 프로젝트에 라우트 그룹, 동적 라우트, Server Action, middleware 각각 손으로 짜기. 책: The Road to Next(robinwieruch.de).

WEEK 3

shadcn/ui + Tailwind v4 디자인 시스템

실습: npx shadcn@latest init으로 새 프로젝트 만들고 OpenStock에 있는 모든 components/ui/*를 추가해보기. Tailwind v4의 @theme으로 자기만의 컬러 토큰 정의. 다크모드 변수 분리.

WEEK 4

MongoDB + Mongoose 모델링

읽기: MongoDB University M001 (무료 코스). 실습: 워치리스트에 더해 가격 알림, 포트폴리오, 거래 일지 컬렉션 설계. 인덱스 전략({userId:1, symbol:1} 같은 복합 인덱스) 이해.

WEEK 5

Better Auth 깊이

이메일/비밀번호 → OAuth(Google, GitHub) → Magic Link → 2FA(TOTP) 순차 추가. sendResetPassword 콜백처럼 후크 포인트들 익히기. 세션 쿠키 SameSite/Secure 설정도 직접 다뤄보기.

WEEK 6

Inngest 서버리스 패턴

읽기: Inngest 공식 docs의 "step functions", "concurrency", "throttling" 챕터. 실습: 가격 알림 함수에 concurrency: 10 제한 추가해서 외부 API 레이트리밋 보호.

WEEK 7

Docker + Vercel 배포 자동화

OpenStock Dockerfile을 멀티스테이지 + Next.js standalone 출력으로 리팩토링. GitHub Actions로 main 푸시 시 Docker Hub 푸시 + Vercel 배포 + Lighthouse 점수 체크 워크플로우 구성.

WEEK 8

AI 통합 패턴 + 멀티 프로바이더 폴백

Gemini → Claude → GPT 순으로 폴백하는 callAIProviderWithFallback 일반화. Vercel AI SDK도 한번 끼워보기. 토큰 비용 로깅, 캐싱, 응답 스트리밍까지.

10핵심 키워드 사전

레포 읽다가 막힐 때 돌아오는 페이지.

프레임워크
App Router (앱 라우터)
Next.js 13부터 도입된 폴더 기반 라우팅 방식. app/about/page.tsx/about 경로. layout.tsx로 공통 UI를 감싼다.
프레임워크
Server Action (서버 액션)
"use server" 지시문이 붙은 함수. 클라이언트에서 함수처럼 호출되지만 실제 실행은 서버. 폼 제출/DB 업데이트의 보일러플레이트를 없앤다.
프레임워크
Turbopack (터보팩)
Webpack 후속을 노리는 Rust 기반 번들러. Vercel이 개발 중. Next.js 15에서는 dev/build 둘 다 안정 단계.
UI
shadcn/ui
npm 패키지가 아니라 CLI로 내 레포에 코드 복사해 넣는 컴포넌트 모음. "내 코드"가 되니까 자유롭게 수정 가능. Radix UI + Tailwind 조합이 표준.
UI
Radix UI Primitives
스타일 없이 접근성·키보드 내비게이션·focus 관리만 담당하는 headless 컴포넌트. 디자인은 사용자가 입힌다.
UI
cmdk
⌘+K(Ctrl+K) 커맨드 팔레트 라이브러리. Linear, Vercel, Raycast 같은 앱의 빠른 검색을 만드는 표준.
백엔드
Better Auth
TypeScript 우선 인증 라이브러리. NextAuth보다 설정이 평탄하고 플러그인 구조. 2024~2025년 새 표준으로 부상 중.
백엔드
MongoDB Adapter
Better Auth 같은 라이브러리가 DB 종류에 상관없이 동작하도록 추상화 계층. users, sessions 컬렉션 자동 생성.
백엔드
Mongoose
MongoDB용 ODM(Object Data Modeling) 라이브러리. JS 객체와 Mongo 문서를 매핑, 스키마 검증·미들웨어·가상 필드 등을 제공.
백엔드
Inngest Step Function
step.run('name', async () => {...})로 작업을 쪼개는 패턴. 각 step은 독립적으로 재시도되며 결과가 영속화된다(중간 실패해도 처음부터 다시 안 함).
데이터
Finnhub API
미국 시장 데이터 SaaS. 무료 티어 + 유료 플랜. WebSocket 푸시도 지원. 한국 시장은 일부만.
데이터
TradingView Widget
차트·시장 개요·뉴스를 iframe/script로 임베드하는 무료 위젯 세트. 진짜 트레이딩 데이터는 별도 구독 필요하지만 시각화는 자유.
AI
Google Gemini 2.5 Flash Lite
구글의 멀티모달 LLM. Flash Lite는 저비용·고속 버전. 분당 한도가 있어 무료 티어로 운영하려면 폴백 전략 필수.
AI
Multi-provider Fallback
한 AI 제공자가 다운되면 다른 제공자로 자동 라우팅. OpenStock은 Gemini→MiniMax(또는 Siray.ai)의 2단 폴백.
인프라
Docker Compose
여러 컨테이너를 YAML 한 파일로 정의·실행. docker compose up으로 앱+DB가 한 번에 뜬다.
인프라
authSource=admin (Mongo URI 옵션)
Mongo root 계정으로 인증할 때 필수. 빠뜨리면 Authentication failed. 신입이 가장 자주 막히는 부분.
라이선스
AGPL-3.0 (Affero GPL v3)
GPL의 강화판 — 네트워크 서비스로 제공해도 소스를 공개해야 한다. 즉 SaaS로 팔아도 깃허브에 올려야 함. "지식은 무료"라는 OpenStock 철학을 법적으로 잠그는 장치.
개념
Copyleft (카피레프트)
"이 코드를 쓰려면 너도 똑같이 풀어야 한다"는 조건의 오픈소스. Copyright의 반대말 같지만 실제론 저작권을 이용해서 자유를 강제하는 영리한 장치.
개념
Cross-cutting Concern (횡단 관심사)
로깅·인증·에러처리처럼 여러 모듈에 걸쳐 등장하는 공통 로직. Next.js의 middleware.ts가 대표적인 해결책.

11참고 링크

더 깊이 파고들 길.

레포 자체

공식 문서

외부 서비스

학습 자료

실용 가이드

지금 시도해볼 만한 것들

  1. 10분 — 데모 체험. openstock-ods.vercel.app 접속, 회원가입, ⌘+K로 AAPL 검색, 워치리스트에 별표. 환영 메일이 진짜 오는지 받은편지함 확인.
  2. 30분 — 로컬 클론 + Docker Compose. git clone.env 셋업(Finnhub 키만 우선) → docker compose up -d. localhost:3000에서 자기 인스턴스 띄우기.
  3. 1시간 — 키워드 사전 정독 + 코드 산책. 위 10번 키워드 사전 한 번 훑고, lib/inngest/functions.ts를 라인 하나하나 읽으며 step.run 흐름 따라가기.
  4. 주말 — 실습 과제 1번 (한국어 검색). lib/constants.ts에 매핑 테이블 만들고, PR 보낼 정도로 다듬으면 진짜 OSS 컨트리뷰션 데뷔.
  5. 한 달 — 자기 버전 포크. 자기 관심사(암호화폐? ETF? 한국 주식?)에 맞게 데이터 소스 교체. 디자인도 자기 톤으로. 포트폴리오·이력서에 라이브 URL 첨부.
원문 · Open-Dev-Society/OpenStock 레포 · README · API_DOCS.md · package.json · Dockerfile · docker-compose.yml · lib/inngest/functions.ts · 2026-05-28 분석 · github.com/Open-Dev-Society/OpenStock