Open-source directory of startup credits, discounts, and eligibility requirements.
The goal is simple: help founders find real perks in one clean place, then let the community keep the list current through pull requests.
- Astro (latest)
- Bun
- Astro Content Collections with schema validation
bun install
bun run devOpen http://localhost:4321.
Optional environment variables:
PUBLIC_REPO_URL=https://github.com/your-org/startupperks
PUBLIC_REPO_BRANCH=main
PUBLIC_SUBMISSION_API_URL=https://startup-perks-submit-api.<your-subdomain>.workers.dev/api/submit-perk
PUBLIC_SITE_URL=https://startup-perks.comThe repo currently defaults to https://github.com/jnd0/startup-perks and main if these are not set. Set these vars only if you want to override those defaults.
If PUBLIC_SUBMISSION_API_URL is set, the submit form creates a PR automatically via the Worker API, so contributors do not need GitHub accounts.
PUBLIC_SITE_URL controls canonical URLs and sitemap domain during build.
- Cloudflare Dashboard -> Workers & Pages -> Create -> Pages -> Connect to Git.
- Select this repository and the default branch.
- Configure build settings:
- Build command:
bun run build - Build output directory:
dist - Root directory: repository root
- Build command:
- Set environment variables for Preview and Production:
PUBLIC_REPO_URL=https://github.com/your-org/startupperksPUBLIC_REPO_BRANCH=mainPUBLIC_SITE_URL=https://startup-perks.com
- Save and deploy.
bun add -D wrangler
bunx wrangler login
bun run build
bunx wrangler pages deploy dist --project-name startupperksIf you want to publish as a Worker with static assets instead of Pages, this repo includes wrangler.jsonc.
bun run build
bunx wrangler deployNotes:
- This project is static Astro output (
dist/), so no Cloudflare adapter is required. - Bun is only needed at build time. If your Pages image does not support Bun, use
npm run buildas fallback. - Cloudflare Workers with static assets do not support runtime Worker variables; use build-time envs or rely on the repository defaults in this codebase.
- SEO assets are generated at build time:
dist/sitemap-index.xmlandpublic/robots.txt.
Outbound "Claim Offer" links on perk pages go through a private redirect route instead of linking directly to the provider:
/perks/shor-payroll/ -> Claim Offer -> /go/shor-payroll/ -> 302 to the perk's applyUrl
How it works:
- An Astro integration in
astro.config.mjsregeneratesfunctions/go/_map.tson every build. It maps every active perk id to itsapplyUrl, so adding a perk makes it trackable automatically. SetDISABLE_REDIRECTS=1at build time to fall back to direct provider links. - The production Worker (
worker/index.ts, wired up bywrangler.jsoncviamain+ theASSETSbinding) serves static assets first and handles/go/*requests itself, so redirects work on the deployed Worker. bun run devhandles/go/*with a dev-server middleware injected by the same integration (redirect only, no counting).- Cloudflare Pages deployments are also supported via
functions/go/[slug].ts. - Clicks are recorded with an atomic SQL upsert (
INSERT ... ON CONFLICT ... DO UPDATE SET clicks = clicks + 1) against a D1 database, so concurrent clicks never overwrite each other. The write runs throughwaitUntil, so redirects are never delayed by it, and a missing/misconfigured table logs an error without breaking the redirect.
To enable counting in production, create a D1 database and bind it once:
bunx wrangler d1 create startupperks-statsRun the table DDL (exported as CLICKS_TABLE_DDL in shared/go-redirect.ts):
bunx wrangler d1 execute startupperks-stats --command "CREATE TABLE IF NOT EXISTS perk_clicks (slug TEXT PRIMARY KEY, clicks INTEGER NOT NULL)" --remoteThen uncomment the d1_databases block in wrangler.jsonc, fill in the returned database_id, and redeploy.
To read click counts:
bunx wrangler d1 execute startupperks-stats --command "SELECT slug, clicks FROM perk_clicks ORDER BY clicks DESC" --remoteCounts live only in your D1 database and are not exposed on the public site.
A private dashboard renders the click counts as a simple page:
https://startup-perks.com/admin/dashboard?key=<ADMIN_KEY>
- Requires an
ADMIN_KEYsecret on the Worker. Requests with a missing or wrong key get401 Unauthorized; the check runs before any database access and uses constant-time comparison of keyed hashes. - Add
&format=jsonto get the same data as JSON ({ totalClicks, perks: [{ slug, clicks }] }) for scripts or exports. - Pages always send
Cache-Control: no-store, so nothing is cached at the edge. - Locally (
bunx wrangler dev --local), setADMIN_KEY=<value>in.dev.vars(gitignored). Without a D1 binding the page shows a friendly fallback instead of erroring.
Set the secret once:
bunx wrangler secret put ADMIN_KEYUse a long random value (for example openssl rand -hex 24). Rotating the key replaces the secret immediately. The key travels in the URL, so treat the dashboard URL like a password: do not share it publicly.
Note: astro preview serves plain static output and does not handle /go/*; use bun run dev or a deployed Worker to exercise redirects.
The repository includes a separate Worker API at workers/submit-api/ for automatic PR creation.
Deploy it with:
bunx wrangler secret put GITHUB_TOKEN --config workers/submit-api/wrangler.toml
bunx wrangler deploy --config workers/submit-api/wrangler.tomlThen set PUBLIC_SUBMISSION_API_URL in your frontend deployment to:
https://startup-perks-submit-api.<your-subdomain>.workers.dev/api/submit-perk
/
├── src/
│ ├── content/perks/ # One markdown file per perk
│ ├── components/ # UI components
│ ├── layouts/ # Shared layout and metadata
│ └── pages/ # Astro routes
├── functions/go/ # Pages Function redirect entry (Pages deployments)
│ └── _map.ts # Generated at build time (gitignored)
├── worker/ # Production Worker entry: assets + /go/ redirects
├── shared/ # Redirect logic shared by Worker and Pages Function
├── CONTRIBUTING.md
└── .github/
├── workflows/validate.yml
└── PULL_REQUEST_TEMPLATE.md
- Create a new file in
src/content/perks/. - Use kebab-case filename, for example:
cloudflare-startups.md. - Fill required frontmatter fields (
company,title,eligibility,applyUrl,sourceUrl, etc). - Run the checks locally:
bun run check- Open a pull request.
For full details, see CONTRIBUTING.md.
You can also use the homepage Submit perk form to generate a valid perk file and open GitHub with prefilled content.
- Perk values and eligibility frequently change.
- Every entry should include an official source URL.
- If data is uncertain, mark it clearly in the summary/body and avoid overconfident claims.
- README, CONTRIBUTING, and AGENTS docs are accurate.
.gitignorecovers local/build/deploy artifacts.bun run checkpasses.- License is added before publishing (recommended: Apache-2.0 for code and CC BY 4.0 for content).
- Code in this repository is licensed under
Apache-2.0(seeLICENSE). - Perk/content data in
src/content/perks/is licensed underCC BY 4.0(seeLICENSE-CONTENT).