GitCDN turns a GitHub repository into a lightweight public asset CDN.
Users authenticate with GitHub, select a repo, upload assets into assets/, and get copyable jsDelivr URLs immediately.
- No external storage provider required
- Free-tier friendly deployment on Vercel
- Your files stay in your GitHub repository
- Frontend: React + Vite static build (
dist) - API: Express app running in Vercel Function (
api/[...route].ts) - Auth/session: GitHub OAuth + encrypted
httpOnlycookie session - Asset source: GitHub repository contents
- Public delivery: jsDelivr (
cdn.jsdelivr.net/gh/...)
src/React appsrc/backend/api-app.tsShared API app used by local dev and Vercelapi/[...route].tsVercel serverless entrypointserver.tsLocal development server (API + Vite middleware)vercel.jsonVercel build/output config.env.exampleRequired environment variables
- Node.js 20+
- A GitHub OAuth App
- Vercel account (for deployment)
- Install dependencies:
npm install
- Create local env file:
cp .env.example .env.local
- Fill env vars in
.env.local. - Set GitHub OAuth callback URL to:
http://localhost:3000/api/auth/callback - Start dev server:
npm run dev
- Open:
http://localhost:3000
- Import this repository in Vercel.
- In project settings, configure environment variables:
GITHUB_CLIENT_IDGITHUB_CLIENT_SECRETSESSION_SECRETTOKEN_ENCRYPTION_KEYAPP_URL(recommended)
- Update GitHub OAuth callback URL to:
https://<your-vercel-domain>/api/auth/callback - Deploy.
After deploy, verify these endpoints:
GET /api/healthshould returnok: trueand all required config flags astrue.GET /api/auth/urlshould return a JSON payload with GitHub authorize URL.GET /api/meshould return401before login, then user JSON after login.
If /api/auth/url is 404, redeploy and confirm your deployment includes api/[...route].ts.
If /api/me is 500, check Vercel environment variables (GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET, and one of SESSION_SECRET or TOKEN_ENCRYPTION_KEY).
- Homepage URL: your app URL (local or deployed)
- Authorization callback URL: must exactly match
/api/auth/callback - Example deployed callback URL:
https://gitcdn-lever.vercel.app/api/auth/callback - OAuth scopes requested by app:
repo,user
APP_URL- Optional base URL for callback and origin handling.
- Example:
https://your-app.vercel.app
GITHUB_CLIENT_ID- GitHub OAuth client ID.
GITHUB_CLIENT_SECRET- GitHub OAuth client secret.
SESSION_SECRET- Secret for session/cookie cryptography.
TOKEN_ENCRYPTION_KEY- Optional extra crypto key; if present, preferred over
SESSION_SECRET.
- Optional extra crypto key; if present, preferred over
npm run devRun local API + Vite appnpm run buildBuild static frontend with Vitenpm run previewPreview production frontend buildnpm run lintType-check using TypeScript
You can mint an API app credential from your logged-in dashboard session, then upload files from external services without browser cookies.
POST /api/apps (requires active session + selected repo)
Body (JSON):
{
"name": "Website Upload Bot",
"folder": "api-ingest",
"allowed_extensions": ["png", "jpg", "pdf"],
"max_bytes": 4194304,
"expires_in_days": 90
}Response:
{
"app_id": "app_0123456789abcdef",
"token_type": "Bearer",
"app_secret": "<use-as-bearer-token>",
"ingest_url": "https://your-app.vercel.app/api/ingest/app_0123456789abcdef",
"repo": "owner/repo",
"branch": "main",
"base_folder": "api-ingest",
"allowed_extensions": ["png", "jpg", "pdf"],
"max_bytes": 4194304
}POST /api/ingest/:appId
Headers:
Authorization: Bearer <app_secret>
Payload options:
multipart/form-datawithfile, optionalfolder, optionalfilename, optionalmessage- JSON with
content(data URL or raw base64), optionalfolder, optionalfilename, optionalmessage
Example (multipart):
curl -X POST "https://your-app.vercel.app/api/ingest/app_0123456789abcdef" \
-H "Authorization: Bearer <app_secret>" \
-F "file=@./logo.png" \
-F "folder=api-ingest/marketing"Example response:
{
"success": true,
"app_id": "app_0123456789abcdef",
"repo": "owner/repo",
"branch": "main",
"name": "1740931382996-8b0f5f5b49b9b2b4.png",
"path": "api-ingest/marketing/1740931382996-8b0f5f5b49b9b2b4.png",
"cdn_url": "https://cdn.jsdelivr.net/gh/owner/repo@main/assets/api-ingest/marketing/1740931382996-8b0f5f5b49b9b2b4.png"
}- API app tokens are encrypted and include repo/branch scope.
- If
folderis set during app creation, uploads are restricted to that folder subtree. - Current absolute server-side cap is 15 MB (
max_bytesmust be <= 15 MB). - If the target filename already exists, upload returns
409.
- Works without paid infrastructure (no managed DB required).
- Vercel Hobby has usage and fair-use limits.
- GitHub API rate limits apply.
- Large files and heavy traffic may require moving to paid tiers later.
- Session is cookie-based, so avoid storing large extra user state.
- GitHub repository permissions and rate limits drive API behavior.
- Private repository assets are not public via jsDelivr.
See project.md for the product flow, MVP scope, and UX notes.
LICENSE(MIT)CODE_OF_CONDUCT.mdCONTRIBUTING.mdSECURITY.md.github/pull_request_template.md.github/ISSUE_TEMPLATE/*