A production-ready GitHub integration for Next.js apps, modeled after Vercel's GitHub connector. Supports public and private repositories via GitHub Apps (not OAuth).
┌──────────────────┐ ┌───────────────────┐
│ Your Next.js │ ◀──────│ GitHub Webhooks │
│ App │ HMAC │ (push, install) │
└────────┬─────────┘ └───────────────────┘
│
│ short-lived installation tokens (~1h)
│ encrypted (AES-256-GCM) at rest
│
▼
┌──────────────────────────────────────────────────┐
│ GitHub REST API │
│ /repos, /branches, /commits, /contents │
└──────────────────────────────────────────────────┘
- GitHub App auth. App JWT (RS256) → installation access token exchange, encrypted caching with auto-refresh.
- Webhooks. Receiver verifies
X-Hub-Signature-256, dedupes viaX-GitHub-Delivery, dispatchespush/installation/installation_repositories/pull_request. - Encryption. AES-256-GCM with per-record IVs, used for installation tokens and any other secret you encrypt.
- Security. CSRF (double-submit cookie), OAuth state, HSTS + CSP, rate limiting hook.
- UI. Vercel-style black/white dashboard, repo list with search and public/private filter, repo detail with branch switcher and commit list.
# 1. Install
pnpm install
# 2. Set up a GitHub App
# → see docs/GITHUB_APP_SETUP.md
# 3. Configure environment
cp .env.example .env.local
# Generate the two secrets you need:
echo "ENCRYPTION_KEY=\"$(openssl rand -base64 32)\""
echo "GITHUB_WEBHOOK_SECRET=\"$(openssl rand -hex 32)\""
# Fill in the rest from your GitHub App settings page
# 4. Database
pnpm prisma migrate dev --name init
# 5. Run
pnpm devVisit http://localhost:3000 and click Connect GitHub.
| File | What it covers |
|---|---|
docs/GITHUB_APP_SETUP.md |
Creating the GitHub App, permissions, generating credentials |
docs/ENVIRONMENT.md |
Every env var, how to generate it, how to rotate keys |
docs/DEPLOYMENT_VERCEL.md |
Postgres provider choice, env config on Vercel, smoke tests, hardening checklist |
github-connector/
├── prisma/
│ └── schema.prisma # User, GitHubInstallation, EncryptedToken, Repository, WebhookDelivery
├── docs/
│ ├── GITHUB_APP_SETUP.md
│ ├── ENVIRONMENT.md
│ └── DEPLOYMENT_VERCEL.md
├── src/
│ ├── app/
│ │ ├── (dashboard)/ # /connect, /repositories, /repositories/[id]
│ │ ├── api/
│ │ │ ├── github/ # install, callback, repos, branches, commits, contents, disconnect
│ │ │ └── webhooks/github/ # webhook receiver
│ │ ├── layout.tsx
│ │ ├── globals.css
│ │ └── page.tsx
│ ├── lib/
│ │ ├── github/ # JWT, token exchange, octokit factory, webhook handlers
│ │ ├── crypto/ # encrypt, decrypt, webhook signature verify
│ │ ├── auth/ # session stub, OAuth state, CSRF
│ │ ├── ratelimit/ # in-memory limiter (swap for Upstash in prod)
│ │ ├── db/ # Prisma singleton
│ │ └── env.ts # Zod-validated env, fails fast on bad config
│ ├── components/ # ConnectGitHubButton, RepositoryList, RepositoryCard, BranchSelector, CommitList + ui/
│ ├── types/
│ │ └── github.ts # lean shapes for installations, repos, branches, commits, webhooks
│ └── middleware.ts # security headers, CSRF token issuance
├── .env.example
├── tailwind.config.ts
├── tsconfig.json
├── next.config.ts
└── package.json
Why GitHub Apps, not OAuth Apps. Per-repo access controlled by the user, short-lived tokens (~1h), native webhooks, higher rate limits (5,000/hr per installation), revocable instantly via uninstall. Long version in docs/GITHUB_APP_SETUP.md.
Auth flow.
/api/github/install— mint OAuth state cookie, redirect togithub.com/apps/{slug}/installations/new.- User picks repos on GitHub.
- GitHub →
/api/github/callbackwithinstallation_id+state. Verify state, fetch install metadata, sync repo list, redirect to/repositories. - Any subsequent GitHub call:
octokit-factorylooks up cached encrypted token, refreshes 5min before expiry, returns auth'd Octokit.
Trust boundary. Frontend never sees a token, never sees the private key, never sees the installation ID in a usable form. All GitHub calls happen in route handlers. UI talks to /api/... which talks to GitHub.
Idempotency. Webhook deliveries are deduped by X-GitHub-Delivery. All handlers use upsert/deleteMany so retries are safe.
- App user authentication. The connector ships with a thin stub (
src/lib/auth/session.ts). Bring your own — NextAuth, Clerk, Supabase, custom. The stub is a one-function replacement; see the integration snippets indocs/DEPLOYMENT_VERCEL.md. - Write operations on repos. Read-only by design (Contents: Read on the App permissions). Adding write is a permission change + new route handlers; nothing in the architecture prevents it.
- Production rate-limit store. The in-memory limiter works for one instance. Swap for Upstash Redis on serverless — the file documents the exact 6-line change.
MIT. Use it, fork it, ship it.