ViteReactStarter

cavuno-tanstack-start-shadcn-job-board-template — Vite Starter Template

By wollemiahq

A job board starter template built on TanStack Start and shadcn/ui, connecting to a real backend via the Cavuno SDK and running on sandbox data right after cloning.

Explore the design

Compare the page layouts and mobile experience. Open any screenshot for a closer look.

3 previews
Desktop homepage1 / 3
Desktop captureTop of page

Actual screenshot captured by the server browser.

All pages & views 3

Screenshots show visual appearance; interactive behavior may differ.

Recorded 2026-10-02View source

What stands out

A quick read of the recorded project description.

01

Runs immediately after cloning with no API keys, accounts, or database, connecting directly to the Cavuno sandbox board

02

Includes 8 preset roles that can be switched via the developer preview toolbar, setting a real session cookie

03

Employer-side applicant tracking is presented as a kanban board with mouse and keyboard drag-and-drop and optimistic updates

04

Capture-based outbound email viewer enables local completion of login, verification, password reset, and other email flows

05

Sandbox runs Stripe test mode, allowing test cards to complete the candidate paywall and job posting flows

06

Built-in SEO capabilities including canonical URLs, JobPosting JSON-LD, sitemap, RSS, and OpenGraph images

Template Details

This is a job board starter template built with TanStack Start, Cloudflare Workers, shadcn/ui (based on Base UI), Tailwind CSS, the Geist font, and Lucide icons. The data layer connects to the Cavuno backend through the Cavuno SDK (@cavuno/board); all pages are server-rendered and call the real Board API rather than using hardcoded or mock data.

The template points by default to the Cavuno platform sandbox board: a fixed tenant that resets nightly and is safe to write to. After cloning, it runs without API keys, accounts, or a database, and ships with 8 preset roles (candidate, employer, and others) that can be switched via the developer preview toolbar in the lower-left corner; switching sets a real session cookie. The sandbox also provides a working applicant tracking system (kanban-style drag and drop), a capture-based outbound email viewer, and Stripe test-mode payments (test card 4242 4242 4242 4242 can be used to complete the candidate paywall and job posting flows). The preview toolbar appears only under the sandbox board and disappears automatically once you point to your own board.

Routes cover the homepage, job search and detail pages, programmatic SEO job listings, company and salary pages, talent directory, blog, candidate/employer authentication, candidate account and messages, employer workspace (including the applicant kanban), the job posting funnel, content and legal pages, RSS/sitemap/robots and other feed and discovery endpoints, and OpenGraph assets. SEO features include canonical URLs, JobPosting JSON-LD, sitemap, RSS, and OpenGraph images.

The code is organized by layer: an SDK layer (formatting, paths, breadcrumbs, copy), a view-model mapping layer (pure functions under src/board/**), and a presentation layer (stateless components under src/components/**), along with AGENTS.md as a contract for coding agents and version-matched Cavuno Agent Skills.

Known limitation: in development, the first request to /embed/jobs right after starting pnpm dev returns a 500; refreshing once resolves it, and visiting other routes first also avoids it. Production builds are unaffected. Requires pnpm 11 and Node 24.

ViteReactStarter

Is it right for your project?

Use Cases

  • Quickly building a job board frontend backed by real backend data
  • Demonstrating candidate applications, employer screening, and payment flows on sandbox data
  • Serving as a reference implementation combining TanStack Start and shadcn/ui with the Cavuno SDK
  • Providing coding agents with a clearly layered, contract-defined job board codebase

Suitable For

  • Product teams needing a runnable job board prototype
  • Frontend developers using TanStack Start and shadcn/ui
  • Engineers who want to develop against a real backend rather than mock data
  • Recruitment projects needing programmatic SEO job pages and salary pages

Getting started & customization

Use these recorded setup notes alongside the repository instructions for your chosen version.

  1. 1

    Requires pnpm 11 (pinned in package.json) and Node 24

  2. 2

    After cloning, copy .dev.vars.example to .dev.vars, which already points to the sandbox board

  3. 3

    Run pnpm install and pnpm dev; it starts at http://localhost:3000 by default

  4. 4

    When going live, replace CAVUNO_BOARD with your own board's pk_ publishable key (in .dev.vars for development, in wrangler.jsonc for production)

  5. 5

    In development, if the first request to /embed/jobs returns a 500, refresh once; production builds are unaffected

Read repository instructions

License & source material

Usage conditions & limitations

Free to use · keep license notice

This entry covers the free version in the public repository under the MIT license; the free scope applies to that version's code and does not imply that external services or third-party assets are free.

Must comply with when using

  • You may use, modify, commercialize, and distribute this version's code for free.
  • When distributing copies or substantial portions of the code, retain the original author's copyright notice and the full MIT license notice.
  • The code is provided as is; warranties and limitations of liability are governed by the original license and applicable law.

Additional costs & items to verify

  • Demo images, fonts, icons, and other third-party assets must be checked against their respective licenses; the code's MIT license alone is not sufficient to judge them.
  • External services such as domains, hosting, databases, payments, or AI may incur separate charges.
  • By default it points to the Cavuno platform sandbox board: a fixed tenant that resets nightly, is safe to write to, and is intended only for development preview.
  • The developer preview toolbar (role switching, email viewing, reseeding, toggles) appears only under the sandbox board and is no longer shown once you point to your own board.
  • Going live requires a human operator to set CAVUNO_BOARD and CAVUNO_API_URL in wrangler.jsonc, pointing to your own Cavuno board.
  • The runtime requires pnpm 11 and Node 24.
  • Known development defect: the first request to /embed/jobs right after starting pnpm dev returns a 500; refreshing once resolves it. Production builds are unaffected.
  • The Board API may only be called server-side (src/server/**), not directly from the client.
  • Production ships in English only by default, with no language switcher or language-prefixed routes; other language catalogs must be enabled manually.
  • Interface copy can be localized, but board content such as jobs and companies always uses the board's own language.
  • Enabling Google-hosted bottom-anchored ads requires manual configuration in the AdSense dashboard; the template itself does not set it up.
  • Exclusion rules must be set in AdSense for private/tool routes and the mobile Apply conflict page.
  • Preview mode does not remove third-party scripts that were already loaded before it was enabled.
  • Where cookie consent is required, conversion events are queued in dataLayer first, and GTM and pixels load only after the user accepts.
  • Analytics configuration must use only one destination per vendor to avoid double counting.
  • @cavuno/board and vite-plus are deliberately pinned to exact versions and should not be changed to floating versions.
  • Deployment values such as CAVUNO_BOARD and CAVUNO_API_URL should not be modified by coding agents.
View Original Text Basis

Permission is hereby granted, free of charge

Original License Text

`.dev.vars.example` already points at the **platform sandbox board** — a live, deterministic fixture tenant that resets nightly and is safe to write to.

Original README

The preview toolbar is sandbox-only.

Original README

Going live means pointing the frontend at your own board: a human operator sets `CAVUNO_BOARD` (and `CAVUNO_API_URL`) in `wrangler.jsonc` before deploying.

Original README

Requires **pnpm 11** (pinned in `package.json`) and Node 24 (matches CI).

Original README

if the very first request to a freshly booted > `pnpm dev` is `/embed/jobs`, it answers 500 ("Something went wrong") and then > works on every request after.

Original README

The Board API is only ever called server-side (`src/server/**`).

Original README

Production ships English only (no language switcher or locale-prefixed routes).

Original README

the UI localizes while board content (jobs, companies) stays in the board's own language

Original README

Manual units scroll with the page. To enable a Google-managed bottom anchor, configure AdSense → Ads → your site: Auto ads on, Anchor ads on, Bottom only, Allow dynamic anchors off, and desktop anchors on.

Original README

Use AdSense page exclusions for private/utility routes and mobile Apply conflicts: a script already loaded can remain active during client navigation.

Original README

Preview does not remove third-party scripts already loaded before it was enabled.

Original README

When cookie consent is required, conversion events still push to `window.dataLayer` immediately; GTM and pixels load only after accept, then flush any queued pixel calls.

Original README

use one destination per vendor to avoid double-counting

Original README

`@cavuno/board` and `vite-plus` are pinned **exact** on purpose

Original README

which is why [`AGENTS.md`](AGENTS.md) tells coding agents to never touch these values.

Original README
https://github.com/wollemiahq/cavuno-tanstack-start-shadcn-job-board-template/blob/main/LICENSEhttps://github.com/wollemiahq/cavuno-tanstack-start-shadcn-job-board-template/blob/main/README.md

Compiled by DeepSeek based on repository license and README ·2026-10-04. Results only cover the materials read; dependencies, images, fonts, and subsequent term changes are subject to their respective original texts.

Project checks & description sources

Template Information

Source platforms
GitHub
Documentation and maintenance reference score
More documented · 88 / 100
Technologies
Vite · React
Website type
Starter
How this score is calculated

Our heuristic uses license records (up to 30), a demo link (12), a preview (10), stars (15), recorded maintenance (15), description completeness (8), feature records (5) and classification data (5). It is not a code, security or performance test, or a Google score. A link does not establish tested functionality.

What to verify for this project

Repository update recorded
2026-10-02
Screenshot last checked
2026-10-04

A demo link is listed. Check it with your own content and viewport sizes; its presence does not confirm that forms, authentication or payments work.

The catalog identifies Vite as the primary stack. Check the actual dependency manifest and lockfile for versions and hosting requirements. These notes use recorded metadata, not a code audit or a deployment test.

Use a selection guide →

How this description was prepared

AI-assisted description based on catalog records and repository material. Repository authors are not automatically the reviewers of this description.

Review declaration: [email protected] · 2026-10-04

Inspect the recorded source material

SHA-256: c6a9851c13387171137c4dfeb6acd39c116a3fd2820d8a5d6416c811e4a4836d

2026-10-04T12:25:28.547Z

Source file / repository
# TanStack Start + shadcn/ui job board template — powered by Cavuno

**Clone it, `pnpm dev`, and in under a minute you have a populated,
production-realistic job board** — real jobs and companies, a working
applicant tracker, captured outbound emails, and Stripe test-mode checkout.
No API key, no account, and no empty database to seed first.

Built on [TanStack Start](https://tanstack.com/start),
[Cloudflare Workers](https://workers.cloudflare.com),
[shadcn/ui](https://ui.shadcn.com) on [Base UI](https://base-ui.com),
[Tailwind CSS](https://tailwindcss.com), [Geist](https://vercel.com/font), and
[Lucide](https://lucide.dev). Data comes from [Cavuno](https://cavuno.com)
via the [Cavuno SDK](https://cavuno.com/sdk) (`@cavuno/board`), so every
surface is a real page wired to a real backend — not a mock.

![Cavuno job board home — real sandbox jobs and company cards](docs/media/home.png)

---

## Why this template is different

Most job board templates are a beautiful frontend with no real backend behind
it — the listings are hardcoded or mocked, and to actually launch you either
build the backend yourself (auth, applications, payments, emails, search) or
bolt on a separate service. This one is wired to a real backend from the first
clone, and it boots **populated** so you can see and build every real state
immediately. Two things make that work:

### 1. A zero-setup running product

`.dev.vars.example` already points at the **platform sandbox board** — a live,
deterministic fixture tenant that resets nightly and is safe to write to. So a
fresh clone boots straight into a full board with:

- **8 seeded scenario personas** you switch between from a **developer preview
  toolbar** (bottom-left, sandbox-only) — new candidates, verified candidates,
  premium/granted candidates, and employers from "just signed up" through
  "workspace admin with a live applicant pipeline." Switching sets a **real
  session cookie**, so you see each state exactly as that user would.
- **A working ATS** — the employer applicant pipeline is a real **kanban
  board** (columns are stages, cards are applicants) with pointer **and**
  keyboard drag-and-drop and optimistic moves.
- **Captured outbound email** — a `letter_opener`-style viewer over every email
  the board would send (magic-link sign-in, verification, password reset,
  alert double-opt-in). The sandbox captures instead of delivering, so every
  flow that "continues in your inbox" is completable on the spot.
- **Payments that work with Stripe test cards** — the sandbox runs Stripe
  **test mode**, so the candidate paywall and the job-posting funnel accept
  `4242 4242 4242 4242` with **no Stripe account or keys of your own.**

The full sandbox playbook — persona roster, feature-flag toggles, reseed, and
the headless server-function equivalents — is in
[`docs/preview-states.md`](docs/preview-states.md).

### 2. Agent-ready by construction

[`AGENTS.md`](AGENTS.md) is a machine-readable contract for coding agents
working in this repo. It works because the codebase is layered so that
correctness can't be redesigned away:

- **Layer 1a — the `@cavuno/board` SDK**: formatters, path helpers,
  breadcrumbs, copy. The correctness functions.
- **Layer 1b — view-model mappers** (`src/board/**`): pure functions
  (`toJobCardVM`, `toJobDetailVM`, …) that call the SDK and hand components
  plain, resolved data.
- **Layer 2 — presentation** (`src/components/**`): dumb, typed-props
  components you can restructure freely without ever mis-calling a formatter.

The pinned SDK also ships version-matched **Cavuno Agent Skills** for Codex,
Claude Code, Cursor, and other compatible coding agents. This starter checks
in the Claude-compatible copy under `.claude/skills`; the same corpus can be
installed or refreshed in any existing compatible agent skills directory with:

```sh
npx @cavuno/board setup
```

The skills cover the complete board surface—including TanStack Start and
Cloudflare wiring, auth and session ownership, jobs, companies, salaries,
applications, messaging, SEO, i18n, theming, and runtime verification—so an
agent works from the contracts for the exact SDK version installed here rather
than stale generic documentation. `cavuno-board doctor` also detects when the
checked-in skills no longer match that version.

That separation makes this an **agent-buildable** job board template: an agent
can redesign the surface without breaking salary math, canonical URLs, auth
and session ownership, or SEO.

Homepage text can use [runtime copy templates](docs/home-copy.md) with the
page's existing data, without additional API requests.

---

## Quickstart

```sh
git clone https://github.com/wollemiahq/cavuno-tanstack-start-shadcn-job-board-template
cd cavuno-tanstack-start-shadcn-job-board-template
cp .dev.vars.example .dev.vars   # already points at the live sandbox board
pnpm install
pnpm dev                         # http://localhost:3000
```

That's it. No keys, no account, no database. The dev server boots on the
**sandbox board** and the preview toolbar appears bottom-left — start switching
personas.

> Requires **pnpm 11** (pinned in `package.json`) and Node 24 (matches CI).

> **Known dev-only papercut:** if the very first request to a freshly booted
> `pnpm dev` is `/embed/jobs`, it answers 500 ("Something went wrong") and then
> works on every request after. Refresh once. Any other route hit first also
> avoids it. This is upstream — Vite's on-demand SSR module evaluation hands
> `createSsrRpc` a not-yet-callable `getServerFnById`, so the root shell's
> `getBoardContext()` throws before the page renders
> ([TanStack/router#6451](https://github.com/TanStack/router/issues/6451),
> [#7459](https://github.com/TanStack/router/issues/7459)). `/embed/jobs` is the
> only route that trips it, because it is the only one whose loader calls
> `getBoardContext()` concurrently with the root shell's own call. **Production
> is unaffected** — Rollup resolves the chain statically at build time, so
> `pnpm build && pnpm preview` serves it 200 on a cold first hit.

**The workflow:** build and style your board against the sandbox — it's
populated, safe to write to, and resets nightly — then go live by swapping one
value, `CAVUNO_BOARD`, for your own board's `pk_…` publishable key (`.dev.vars`
in dev, `wrangler.jsonc` vars in production). The app code is identical either
way; you're just pointing it at your real board instead of the sandbox.

> **The preview toolbar is sandbox-only.** Personas, the captured-email
> viewer, reseed, and the flag toggles are gated on the board being the
> platform sandbox (`sandbox: true`), so they exist purely for exploring and
> developing the template. Point `CAVUNO_BOARD` at your own board and the
> toolbar simply isn't there — you're now developing against your real board,
> with real users, real sessions, and real outbound email. That gate is board
> truth from the API, not an env flag, so the toolbar can never render on a
> production board.

| Variable | What | Where |
|---|---|---|
| `CAVUNO_API_URL` | Board API base URL (`https://api.cavuno.com`) | `.dev.vars` / `wrangler.jsonc` |
| `CAVUNO_BOARD` | Your board's `pk_…` publishable key (Dashboard → Settings → API) | `.dev.vars` / `wrangler.jsonc` |

The `pk_…` key is **client-safe by design**; user sessions live in a
host-owned httpOnly cookie this app manages itself. The Board API is only ever
called server-side (`src/server/**`).

---

## Take the tour

The sandbox exists so you can see every state without seeding anything:

1. **Switch personas** — toolbar → **Employers → `employer-admin`**. You are
   now signed in as a workspace admin.
2. **Drag a card across the kanban** — open a job's applicants and move a
   candidate between pipeline stages (mouse or keyboard).
3. **Read a captured email** — toolbar → **Emails** to see the outbound mail
   the board just produced, rendered in full.
4. **Check out with a test card** — trigger the candidate paywall or the
   job-posting funnel and pay with `4242 4242 4242 4242`. No Stripe setup.

![Preview toolbar switching between the 8 seeded personas](docs/media/persona-switcher.png)
![Employer applicant pipeline as a drag-and-drop kanban board](docs/media/kanban.png)
![Captured outbound email viewer](docs/media/captured-emails.png)

---

## What's inside

Every product surface is a real, SSR-rendered page wired to the Board API.
Feeds, metadata, redirects, and other machine endpoints ship alongside them:

| Surface | Route(s) |
|---|---|
| Home (company discovery + latest jobs) | `/` |
| Jobs search (filters + master/detail) | `/jobs`, `/jobs/locations` |
| Job detail (meta + Google for Jobs JSON-LD) | `/companies/:companySlug/jobs/:jobSlug` |
| Programmatic SEO job listings | `/jobs/:keyword`, `/jobs/skills/:skill`, `/jobs/locations/:location`, `/jobs/locations/:location/:keyword`, `/jobs/locations/:location/skills/:skill` |
| Companies, company jobs, and company salaries | `/companies`, `/companies/:companySlug`, `/companies/markets/:market`, `/companies/:companySlug/jobs`, `/companies/:companySlug/salaries`, `/companies/:companySlug/salaries/:categorySlug` |
| Salaries hubs | `/salaries`, `/salaries/companies`, `/salaries/titles`, `/salaries/skills`, `/salaries/locations` |
| Salary title and skill explorers | `/salaries/titles/:slug`, `/salaries/titles/:slug/locations`, `/salaries/titles/:slug/:locationSlug`, `/salaries/skills/:slug`, `/salaries/skills/:slug/locations`, `/salaries/skills/:slug/:locationSlug` |
| Salary location explorers | `/salaries/locations/:slug`, `/salaries/locations/:slug/titles`, `/salaries/locations/:slug/skills` |
| Talent directory and profiles | `/talent`, `/p/:handle` |
| Blog, tags, and authors | `/blog`, `/blog/:postSlug`, `/blog/tag/:tagSlug`, `/blog/author/:authorSlug` |
| Candidate and employer authentication | `/auth/sign-in`, `/auth/sign-up`, `/auth/employer/sign-up`, `/auth/join`, `/auth/magic-link`, `/auth/oauth-complete`, `/auth/forgot-password`, `/auth/reset-password`, `/auth/verify-email`, `/auth/verify-email-required`, `/auth/verify-work-email` |
| Candidate profile, applications, saved jobs, and settings | `/account`, `/matches`, `/saved-jobs`, `/account/access`, `/me/applications`, `/settings` |
| Candidate alerts and messaging | `/me/alerts`, `/alerts/confirm`, `/alerts/manage`, `/messages`, `/messages/:conversationId` |
| Employer entry and onboarding | `/employers`, `/employers/dashboard`, `/employers/onboarding/:slug` |
| Employer company workspace (jobs + stats chart, **applicant pipeline kanban**, company profile) | `/employers/companies/:slug`, `/employers/companies/:slug/jobs/new`, `/employers/companies/:slug/jobs/:jobId/edit`, `/employers/companies/:slug/jobs/:jobId/applicants`, `/employers/companies/:slug/profile` |
| Post a job (anonymous funnel) | `/post` |
| Board access and embeds | `/password`, `/embed/jobs` |
| Content and legal pages | `/about`, `/privacy-policy`, `/terms-of-service`, `/cookie-policy`, `/impressum` |
| Feeds and discovery | `/jobs/rss.xml`, `/blog/rss.xml`, `/sitemap.xml`, `/sitemap/:file`, `/robots.txt`, `/indexnow-key.txt` |
| OpenGraph assets | `/companies/:companySlug/jobs/:jobSlug/og`, `/blog/:postSlug/og`, `/blog/og/:postSlug.json` |
| Platform and integration endpoints | `/.well-known/cavuno.json`, `/site.webmanifest`, `/ads.txt`, `/go/*` |

The same supported surfaces stay unprefixed by default (English). Extra
chrome locales (`/de/…`, `/fr/…`) are opt-in — `pnpm locale:add de`.

Cross-cutting capabilities that ship on top of those routes:

- **SEO built in** — canonical URLs, `JobPosting` JSON-LD (Google for Jobs),
  `sitemap.xml`, `robots.txt`, job and blog RSS feeds, and OpenGraph images.
  Job-detail URLs mirror Cavuno's hosted board, so a board migrating hosted →
  headless keeps its indexed URLs.
- **Real-data discipline** — the design handles messy data by construction:
  long titles clamp, absent salaries are omitted (never an empty label), skill
  tags cap at `3 + N`, missing logos fall back to initials.
- **i18n ready, English by default** — [Paraglide JS](https://paraglidejs.com)
  compile-time catalogs, SSR-resolved on Workers. Production ships English
  only (no language switcher or locale-prefixed routes). German, French,
  Spanish, Polish, and Dutch catalogs and legal templates ship dormant;
  `pnpm locale:add nl` enables Dutch (or use another catalog's locale code).
  Run `pnpm gen:messages && pnpm gen:paraglide` to compile; the footer
  switcher then appears. See [localization](docs/localization.md) for Dutch
  terminology and verification. Deliberately
  chrome-only: the UI localizes while board content (jobs, companies) stays
  in the board's own language, matching the platform's single-language
  board model.
- **Dark mode** keyed to a single `.dark` class, and **accessibility** from
  Base UI semantics and the owned components' explicit ARIA contracts.

---

## Tech stack

| Layer | Choice |
|---|---|
| Framework | [TanStack Start](https://tanstack.com/start) (SSR) on React 19 |
| Runtime | [Cloudflare Workers](https://workers.cloudflare.com) |
| Build | Vite+ (`vp`) |
| UI | [shadcn/ui](https://ui.shadcn.com) on [Base UI](https://base-ui.com), [Tailwind CSS 4](https://tailwindcss.com), [Geist](https://vercel.com/font), [Lucide](https://lucide.dev) |
| Backend | [Cavuno](https://cavuno.com) — the hosted job board platform |
| Data | [Cavuno SDK](https://cavuno.com/sdk) (`@cavuno/board`) |
| i18n | [Paraglide JS](https://paraglidejs.com) |

Exact versions are in [`package.json`](package.json). `@cavuno/board` and
`vite-plus` are pinned **exact** on purpose — npm's `min-release-age` policy
would otherwise silently resolve `@latest` down to an older version.

---

## Advertising

`board.context().ads` supplies `enabled`, `clientId`, and `defaultSlotId`.
The starter uses the board's default unit for manual placements. Missing or
invalid IDs render nothing, and the advertising switch and cookie-consent gate
apply even to custom slot IDs. Older APIs without `defaultSlotId` remain usable;
manual placements stay off until the updated API is deployed.

- Job and company details: 300×250 in the existing desktop sidebar (1024px+).
- Blog articles: 300×250 desktop sidebar (1024px+).
- Salary pages retain their full-width content layout; no added ad sidebar.
- Jobs, companies, and talent search: one 160×600 outer rail at 1600px+ width and 900px+ height.
- Public browsing/reading pages load AdSense after consent, without forcing any Auto ads format.

See [placement decisions](docs/advertising.md) for the size and UX rationale.

Manual units scroll with the page. To enable a Google-managed bottom anchor,
configure AdSense → Ads → your site: Auto ads on, Anchor ads on, Bottom only,
Allow dynamic anchors off, and desktop anchors on. Disable other Auto ads formats
unless wanted. The starter does not set `data-overlays` or render a footer placeholder.
Use AdSense page exclusions for private/utility routes and mobile Apply conflicts:
a script already loaded can remain active during client navigation.

The shared `BoardAdsProvider` supplies defaults. Customize any manual unit in
frontend code without introducing a server-side placement map:

```tsx
<BoardAdSlot placement="custom-sidebar" layout="rectangle" />
<BoardAdSlot placement="custom-sidebar" layout="rectangle" slotId="1234567890" />
```

`src/ads.json` remains a backward-compatible local override: an explicit entry
wins over the default, including a disabled entry. New customizations can pass
`slotId` directly. The second outer search rail remains opt-in via this file.

Hosted working previews automatically show manual ad placeholders and never request live ads, even when placeholders are hidden.

In development or sandbox preview, open the preview toolbar → **Board settings**
→ **Ad placements**. Placeholders read **AD UNIT HERE**, issue no ad requests,
and persist for the browser tab. Only manual placements are previewed;
Google-managed anchors must be checked on the deployed site.
Preview does not remove third-party scripts already loaded before it was enabled.

## Analytics & conversion tracking

Board analytics IDs (`gtmId`, `ga4MeasurementId`, `metaPixelId`,
`linkedInPartnerId`, optional LinkedIn conversion IDs) come from
`board.context().analytics`. The starter supports **two operator paths** —
use one destination per vendor to avoid double-counting:

| Path | When to use |
| --- | --- |
| **Google Tag Manager** | One container ID; wire GA4, Meta, LinkedIn, Google Ads, and custom tags inside GTM |
| **Direct Meta / LinkedIn** | Pixel ID and/or LinkedIn Partner ID only — native `fbq` / `lintrk` at the same moments |

GTM loads on public pages **and** auth/onboarding routes (`/auth/*`,
verify-email, resume onboarding). When cookie consent is required, conversion
events still push to `window.dataLayer` immediately; GTM and pixels load only
after accept, then flush any queued pixel calls.

### Standard `dataLayer` conversion events

Use these as **Custom Event** triggers in GTM (field names match hosted Cavuno
boards — [operator docs](https://cavuno.com/docs/analytics/connect-google-tag-manager)):

| Event | When | Key fields |
| --- | --- | --- |
| `sign_up` | Account created (password/OAuth/magic-link). Password: auto-redirect to verify-email landing with `cavuno_auth*`, not after OTP | `method`, `board_slug` |
| `login` | Successful sign-in | `method`, `board_slug` |
| `apply_click` | Apply flow opened (not the registration wall) | `job_id`, `job_slug`, `company_slug`, `apply_type`, `board_slug` |
| `apply_submit` | Native application submitted | `job_id`, `application_id`, `job_slug`, `company_slug`, `board_slug` |
| `job_alert_subscribe` | Job alert confirmed (double opt-in) | `board_slug`, `source` (`confirm`) |

Auth methods: `password`, `google`, `linkedin`, `magic_link`.

---

## Customize it

Make this board your own: change its layout, visual identity, navigation, and
features while keeping the integrations and behavior your users rely on.

- **[`AGENTS.md`](AGENTS.md)** — concise guidance for contributors and agents.
- **[`DESIGN.md`](DESIGN.md)** — the visual identity, design tokens, and the
  full component inventory (generated from `src/theme.css` + component source;
  never hand-edited).
- **[`docs/patterns/`](docs/patterns/README.md)** — examples of existing page compositions
  to adapt or replace when a different design serves the request.

It's standard [shadcn/ui](https://ui.shadcn.com) on Base UI: the primitives
under `src/components/ui/` are token-pure and the theme lives in the
shadcn-CLI-owned `src/theme.css`. So you can restyle the whole board with any
shadcn theme — pick one at [ui.shadcn.com/create](https://ui.shadcn.com/create)
and apply it, or run `pnpm run theme:apply`. The full theming workflow is in
[`docs/theming.md`](docs/theming.md) and [`DESIGN.md`](DESIGN.md).

---

## Deploy

The template targets **Cloudflare Workers** (config in
[`wrangler.jsonc`](wrangler.jsonc)).

```sh
pnpm run deploy            # builds, then `wrangler deploy`
```

Going live means pointing the frontend at your own board: a human operator
sets `CAVUNO_BOARD` (and `CAVUNO_API_URL`) in `wrangler.jsonc` before
deploying. That deploy-time grounding is the operator's job, not a code edit
— which is why [`AGENTS.md`](AGENTS.md) tells coding agents to never touch
these values.

[![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/wollemiahq/cavuno-tanstack-start-shadcn-job-board-template)

---

## Verify

CI runs the complete release checks. During development, use the checks
relevant to the change; run the full sequence when validating a release locally:

```sh
pnpm run typecheck && pnpm test && pnpm run build
```

The release checks, including formatting and the Board
API conformance probe (`cavuno-board doctor`), is documented in
[`CONTRIBUTING.md`](CONTRIBUTING.md) and [`docs/testing.md`](docs/testing.md).

## Performance

Core Web Vitals are held to budgets, not vibes. One command builds the
production bundle, serves it the way a CDN would (Brotli, warmed anonymous
HTML cache), and runs Lighthouse's default mobile lab — simulated Slow 4G,
4× CPU throttle — across ten routes (home, listings, and a discovered
job / company / salary / blog-post detail page):

```sh
pnpm run perf:lighthouse:check
```

The check fails unless every route meets, on every run:

| Budget | Target |
| --- | --- |
| Performance score | ≥ 90 worst run, ≥ 95 median |
| Largest Contentful Paint | ≤ 2.5 s |
| Total Blocking Time | ≤ 150 ms |
| Cumulative Layout Shift | ≤ 0.05 |

In practice the routes land well inside these: LCP around two seconds in the
mobile lab, with zero main-thread blocking and near-zero layout shift —
`<head>` and JSON-LD are composed server-side, so the LCP content arrives in
the HTML rather than behind a client fetch. Run it on your own fork; the
numbers are only worth what you can reproduce.

---

## Contributing & license

- **[CONTRIBUTING.md](CONTRIBUTING.md)** — how to run, validation, and the
  `AGENTS.md` contract, and PR expectations.
- **[CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)** — Contributor Covenant.
- **License:** [MIT](LICENSE).
Report a correction

Explore similar templates

Compare previews and descriptions within the same stack and category.

react-starter-kit

react-starter-kit

A full-stack SaaS monorepo starter built on Vite and React, with Bun, TypeScript, tRPC, Drizzle ORM, Better Auth, Stripe, and Cloudflare Workers. It includes email OTP, passkey, Google OAuth, organizations, subscription billing, Drizzle migrations, seed scripts, and Terraform infrastructure.

Vite · SaaS

cove

cove

Cove is a minimal TanStack Start starter template maintained by mugnavo, integrating React, TanStack Router/Query, Tailwind CSS, shadcn/ui, Drizzle ORM with PostgreSQL, Better Auth, Vite Plus, and Nitro.

Vite · SaaS

vite-react-boilerplate

vite-react-boilerplate

A production-ready Vite + React starter template with TypeScript, Tailwind CSS, TanStack Router/Query/Table, Storybook, React Hook Form + Zod, Vitest and Playwright testing, plus Husky/Commitizen/Commitlint commit conventions.

Vite · Starter

react-vite-admin

react-vite-admin

A backend management starter built on Vite and React, integrating Recoil, React Query, TypeScript, and Axios, with Ant Design-style interface screenshots. Suitable as a starting point for management-side projects, licensed under MIT, with Chinese documentation and common scripts.

Vite · Admin dashboard

typescript-react_best_practices-vite_template

typescript-react_best_practices-vite_template

A Vite + React + TypeScript starter template by CodelyTV, preconfigured with Jest and React Testing Library unit tests, Cypress end-to-end tests, ESLint and Prettier using Codely's shared configuration, GitHub Actions workflows, a Makefile, and Sass.

Vite · Starter

cloudflare-starter-kit

cloudflare-starter-kit

A Cloudflare Workers project scaffolding template maintained by kriasoft, using Vite and TypeScript, with three built-in workspaces: api, app, and edge, and preconfigured local testing, multiple environments, and a GitHub Actions deployment workflow.

Vite · Starter