克隆后无需 API 密钥、账号或数据库,直接连接 Cavuno 沙箱板运行
cavuno-tanstack-start-shadcn-job-board-template — Vite 启动器模板
作者 wollemiahq
基于 TanStack Start 与 shadcn/ui 的求职招聘板启动模板,通过 Cavuno SDK 连接真实后端,克隆后即可在沙箱数据上运行。
看看你的网站会是什么样
浏览真实页面截图,比较内容排版与手机效果。点击下方缩略图切换,放大查看整页细节。
全部页面与视图 3
截图用于了解视觉与布局,交互效果请以实际项目为准。
值得关注的模板亮点
从已有项目资料中,快速了解它的特点。
内置 8 个预置角色,可通过开发者预览工具栏切换并设置真实会话 cookie
雇主端申请人跟踪以看板形式呈现,支持鼠标与键盘拖拽及乐观更新
捕获式外发邮件查看器,可在本地完成登录、验证、密码重置等邮件流程
沙箱运行 Stripe 测试模式,可用测试卡完成候选人付费墙与职位发布
内置规范 URL、JobPosting JSON-LD、sitemap、RSS 与 OpenGraph 图片等 SEO 能力
模板详细介绍
这是一个求职招聘板(job board)启动模板,使用 TanStack Start、Cloudflare Workers、shadcn/ui(基于 Base UI)、Tailwind CSS、Geist 字体与 Lucide 图标构建。数据层通过 Cavuno SDK(@cavuno/board)连接 Cavuno 后端,所有页面均为服务端渲染并调用真实 Board API,而非硬编码或模拟数据。
模板默认指向 Cavuno 平台沙箱板:一个每晚重置、可安全写入的固定租户。克隆后无需 API 密钥、账号或数据库即可启动,并自带 8 个预置角色(候选人、雇主等),可通过左下角的开发者预览工具栏切换,切换会设置真实的会话 cookie。沙箱还提供可用的申请人跟踪系统(看板式拖拽)、捕获式外发邮件查看器,以及 Stripe 测试模式支付(可用测试卡 4242 4242 4242 4242 完成候选人付费墙与职位发布流程)。预览工具栏仅在沙箱板下出现,指向自有板后自动消失。
路由覆盖首页、职位搜索与详情、程序化 SEO 职位列表、公司页与薪资页、人才目录、博客、候选人/雇主认证、候选人账户与消息、雇主工作区(含申请人看板)、职位发布漏斗、内容与法律页、RSS/sitemap/robots 等 feed 与发现端点,以及 OpenGraph 资源。SEO 方面内置规范 URL、JobPosting JSON-LD、站点地图、RSS 与 OpenGraph 图片。
代码按层组织:SDK 层(格式化、路径、面包屑、文案)、视图模型映射层(src/board/** 纯函数)、展示层(src/components/** 无状态组件),并附带 AGENTS.md 作为面向编码代理的契约,以及版本匹配的 Cavuno Agent Skills。
已知限制:开发环境下若对刚启动的 pnpm dev 首次请求 /embed/jobs 会返回 500,刷新一次即可,其他路由先访问也可避免;生产构建不受影响。需要 pnpm 11 与 Node 24。
适合你的项目吗?
适用场景
- 快速搭建一个带真实后端数据的求职招聘板前端
- 在沙箱数据上演示候选人申请、雇主筛选与支付流程
- 作为 TanStack Start 与 shadcn/ui 结合 Cavuno SDK 的参考实现
- 为编码代理提供分层清晰、契约明确的招聘板代码库
适合人群
- 需要可运行招聘板原型的产品团队
- 使用 TanStack Start 与 shadcn/ui 的前端开发者
- 希望基于真实后端而非模拟数据开发的工程师
- 需要程序化 SEO 职位页与薪资页的招聘类项目
上手与定制指南
结合仓库当前版本的说明,按下面的资料检查内容、配置与运行要求。
- 1
需要 pnpm 11(已在 package.json 中固定)与 Node 24
- 2
克隆后复制 .dev.vars.example 为 .dev.vars,其中已指向沙箱板
- 3
执行 pnpm install 与 pnpm dev,默认在 http://localhost:3000 启动
- 4
上线时将 CAVUNO_BOARD 替换为自有板的 pk_ 发布密钥(开发在 .dev.vars,生产在 wrangler.jsonc)
- 5
开发时若首次请求 /embed/jobs 返回 500,刷新一次即可;生产构建不受影响
授权与来源资料
使用条件与限制
免费使用 · 保留许可声明本次收录的是公开仓库中采用 MIT 许可的免费版本;免费范围为该版本代码,不代表外部服务或第三方素材免费。
使用时需要遵守
- 允许免费使用、修改、商用和分发该版本代码。
- 分发代码副本或实质性部分时,保留原作者版权声明和完整 MIT 许可声明。
- 代码按现状提供;担保与责任限制以原许可证及适用法律为准。
额外费用与待核对事项
- 演示图片、字体、图标及其他第三方素材需按各自许可核对,不能仅凭代码的 MIT 许可判断。
- 域名、托管、数据库、支付或 AI 等外部服务可能另外收费。
- 默认指向 Cavuno 平台沙箱板:一个每晚重置的固定租户,可安全写入,仅用于开发预览。
- 开发者预览工具栏(角色切换、邮件查看、重播种、开关)仅在沙箱板下出现,指向自有板后不再显示。
- 上线需由人工操作员在 wrangler.jsonc 中设置 CAVUNO_BOARD 与 CAVUNO_API_URL,指向自有 Cavuno 板。
- 运行环境要求 pnpm 11 与 Node 24。
- 开发环境已知缺陷:刚启动的 pnpm dev 首次请求 /embed/jobs 会返回 500,刷新一次即可;生产构建不受影响。
- Board API 仅允许在服务端(src/server/**)调用,不可在客户端直接调用。
- 生产默认仅提供英文,无语言切换器或带语言前缀的路由;其他语言目录需手动启用。
- 界面文案可本地化,但职位与公司等板内容始终使用板自身语言。
- 启用 Google 托管底部锚定广告需在 AdSense 后台手动配置,模板本身不设置。
- 需在 AdSense 中为私有/工具路由及移动端 Apply 冲突页面设置排除规则。
- 预览模式不会移除启用前已加载的第三方脚本。
- 需要 Cookie 同意时,转化事件先入 dataLayer 队列,GTM 与像素仅在用户接受后才加载。
- 分析配置需每个厂商只用一个目标,避免重复计数。
- @cavuno/board 与 vite-plus 被刻意固定为精确版本,不应改为浮动版本。
- CAVUNO_BOARD 与 CAVUNO_API_URL 等部署值不应由编码代理修改。
查看原文依据
Permission is hereby granted, free of charge
许可证原文
`.dev.vars.example` already points at the **platform sandbox board** — a live, deterministic fixture tenant that resets nightly and is safe to write to.
README 原文
The preview toolbar is sandbox-only.
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.
README 原文
Requires **pnpm 11** (pinned in `package.json`) and Node 24 (matches CI).
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.
README 原文
The Board API is only ever called server-side (`src/server/**`).
README 原文
Production ships English only (no language switcher or locale-prefixed routes).
README 原文
the UI localizes while board content (jobs, companies) stays in the board's own language
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.
README 原文
Use AdSense page exclusions for private/utility routes and mobile Apply conflicts: a script already loaded can remain active during client navigation.
README 原文
Preview does not remove third-party scripts already loaded before it was enabled.
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.
README 原文
use one destination per vendor to avoid double-counting
README 原文
`@cavuno/board` and `vite-plus` are pinned **exact** on purpose
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.mdwhich is why [`AGENTS.md`](AGENTS.md) tells coding agents to never touch these values.
README 原文
DeepSeek 根据仓库许可证和 README 整理 ·2026-10-04。结果仅覆盖已读取资料,依赖、图片、字体及后续条款变更以各自原文为准。
项目核对记录与介绍依据
模板资料
- 来源平台
- GitHub
- 资料与维护参考分
- 资料较完整 · 88 / 100
- 技术栈
- Vite · React
- 网站类型
- 启动器
这个分数如何计算
本站按许可资料(最高30分)、演示链接(12分)、预览图(10分)、Star数(15分)、记录的维护时间(15分)、描述完整度(8分)、功能记录(5分)和分类资料(5分)计算。它不是代码、安全或性能测试,也不是 Google 评分;有链接不代表已实测可用。
这个项目需要核对什么
- 记录的仓库更新时间
- 2026-10-02
- 截图检查日期
- 2026-10-04
已收录演示链接。请用你的内容与屏幕尺寸检查;有演示不代表表单、认证或支付已经可用。
目录记录的主要技术栈是 Vite。请通过依赖清单和锁文件核对实际版本与托管要求。这些说明基于收录资料,不代表代码审计或部署实测。
查看具体选型方法 →介绍的制作与核对状态
介绍由 AI 辅助整理目录与仓库资料。仓库作者不等于这篇介绍的审核者。
审核声明: [email protected] · 2026-10-04
查看当时使用的来源资料
SHA-256: c6a9851c13387171137c4dfeb6acd39c116a3fd2820d8a5d6416c811e4a4836d
2026-10-04T12:25:28.547Z
来源文件 / 仓库# 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.

---
## 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.



---
## 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.
[](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).
看看同类模板
对比同技术栈与用途的模板,找到更适合的视觉与内容结构。

react-starter-kit
Modern React starter kit with Bun, TypeScript, Tailwind CSS, tRPC, Stripe, and Cloudflare Workers. Production-…
Vite · SaaS

cove
The Cove Stack: 🏝️ minimal TanStack Start template with Vite+, Better Auth, Drizzle ORM, shadcn/ui
Vite · SaaS

vite-react-boilerplate
A production ready, scalable starter template for Vite + React
Vite · 启动器

react-vite-admin
This Starter utilizes React, Recoil, React Query, React Hooks, Typescript, Axios And Vite. 全新技术栈的后台管理系统
Vite · 后台

typescript-react_best_practices-vite_template
<⚡⚛️> Start your React project with Vite following code best practices (TypeScript, Linting, testing, CI/CD…)
Vite · 启动器

cloudflare-starter-kit
Template (boilerplate) repository for scaffolding Cloudflare Workers projects
Vite · 启动器
