Quick links:
- Framework: Next.js (React)
- Styling: Tailwind CSS
- Testing: Vitest (Jest) + React Testing Library
- Component Development: Storybook
- Deployment: Kubernetes (via GitHub Actions)
Non-exhaustive — only the most-touched paths are listed.
apps/website/
├── src/
│ ├── components/ # Reusable UI components
│ ├── pages/ # Next.js pages and API routes (pages router)
│ ├── lib/
│ │ ├── api/ # Server-side API helpers (auth, env, db, route wrappers)
│ │ └── ... # Client/shared helpers (routes, utils, fonts, schemas)
│ ├── hooks/ # Custom React hooks
│ ├── server/ # Server-only modules
│ ├── stores/ # Zustand stores
│ ├── utils/ # Misc client-side utilities
│ ├── types/ # Shared TypeScript types
│ ├── middleware.ts # Next.js middleware
│ ├── instrumentation.ts # Next.js instrumentation hook
│ └── globals.css # Custom Tailwind CSS styles
├── public/ # Static assets (incl. /fonts)
└── scripts/ # Build-time scripts (sitemap, preview rendering)
No special actions needed, just follow the general developer setup instructions.
Common scripts (run from apps/website/):
npm run start # next dev on http://localhost:8000
npm run build # next build (also generates sitemap)
npm run typecheck # tsc --noEmit
npm run lint # eslint (no warnings allowed)
npm run lint:fix # eslint --fix
npm run test # vitest --run
npm run test:watch # vitest in watch mode
npm run test:update # update vitest snapshots
npm run render-preview # render OG/preview images- Create a new file in
src/pages/ - Add the route to the nginx template in website-proxy
- Update the navigation if needed
- Add tests for the new page
Before writing anything new, check what already exists. We have a real shared library and most marketing-page primitives are already in it.
Where to look:
- Run
cd apps/storybook && npm start(or browse storybook.k8s.bluedot.org). - Read
libraries/ui/src/index.tsfor the canonical exports.
@bluedot/ui cheat sheet:
| Need | Use |
|---|---|
| Button or link CTA | CTALinkOrButton (variants: primary, secondary, black, outline-black, ghost; sizes: default, small) |
| Page section / card | Section, SectionHeading, Card |
| Heading / paragraph / link | H1–H4, P, A (or bluedot-h1 / bluedot-p / bluedot-a utility classes) |
| Hero (page top) | HeroSection + HeroH1 / HeroCTAContainer |
| Form input | Input, Textarea, Select, DatePicker, TimePicker, ToggleSwitch |
| Modal / drawer / dropdown | Modal, BottomDrawerModal, OverflowMenu, Tooltip |
| Accordion / show-hide | Collapsible |
| Alert / tag / banner | Banner, Tag, ErrorSection |
| Carousel / list / progress | SlideList, ProgressDots, Breadcrumbs |
| Avatars / share | FaceTiles, ShareButton, SocialShare |
| Auth | LoginRedirectPage, LoginOauthCallbackPage, useAuthStore, withAuth |
| Utils | cn, addQueryParam, maybePlural, asError |
Website-local reusables (in src/components/):
MarketingHero— top-of-page hero with image background + Nav (used on /about, /our-community, /join-us, /grants).PageListRow— list row with leading slot + CTA (used on /grants, /career-transition-grant). Compose withPageListGroupfor divided lists.Nav/Nav— main site nav (variants: default, transparent).Header— page header wrapper.
If you find yourself duplicating one of these, stop and reuse instead.
The token system is documented in apps/storybook/src/GettingStarted.mdx (browse it via Storybook's "Getting Started" intro page).
| Don't write | Write instead |
|---|---|
text-[#0037ff], bg-[#f5f5f5] (raw hex) |
a named palette utility, e.g. text-bluedot-navy, bg-bluedot-normal |
bg-color-primary-accent, border-color-divider (semantic tokens) |
the palette utility it aliases: bg-bluedot-normal, border-charcoal-light, text-bluedot-darker |
text-[16px], text-[18px], text-[24px] |
text-size-sm, text-size-md, text-size-lg |
Prefer the named bluedot-* palette utilities (text-bluedot-navy, including opacity like text-bluedot-navy/60) over the semantic color-* tokens. Size tokens (text-size-*) are still preferred over raw px, though exact pixel values for spacing (e.g. gap-[3px]) are sometimes OK.
- Create the component
.tsxfile insrc/components/ - Add a Storybook story
.stories.tsx— autodocs tag, at least 2 variants, args/argTypes - Write tests in
.test.tsxand run snapshots vianpm run test:update - Use Tailwind classes for styling and BEM classes for identification
- Add a
parameters.design.figmaURL pointing at the Figma node (seeCTALinkOrButton.stories.tsxfor an example)
New backend endpoints should be tRPC routers in src/server/routers/. Don't add pages/api/* routes unless tRPC genuinely won't fit (e.g., non-JSON responses like .ics files, cookie-based auth flows, or fire-and-forget error reporters). The few remaining pages/api/ routes are legacy or special-purpose — see DEVELOPMENT_HANDBOOK.md §4.2 for the list.
- Add a tRPC procedure to the relevant router in
src/server/routers/ - For the rare non-tRPC case, use
makeApiRoutefromsrc/lib/api/as the wrapper - For database access, use the shared
@bluedot/dbpackage — don't bypass it
We use Storybook to develop and test UI components.
To run the Storybook app locally:
# from the root of the repo
npm run start --workspace=apps/storybookOur storybook is deployed to https://storybook.k8s.bluedot.org.
Traffic to the production site (https://bluedot.org) is routed through the website-proxy app.
Important: When adding a new page, you must also add it to the nginx template in website-proxy.
-
Build fails locally
- Clear the
.nextdirectory:rm -rf .next - Delete node_modules:
rm -rf node_modules - Reinstall dependencies:
npm install
- Clear the
-
Storybook not loading
- Ensure you're running from the root directory
- Check if the Storybook workspace is properly configured
This app follows a multistage deployment process. For more details, see docker-scripts.
To deploy to staging, simply merge a PR into the master branch.
To deploy to production:
- Create a new GitHub release
- Click "Choose a tag" and enter a new tag in the format
website/vX.Y.Z(e.g.,website/v1.2.3) - Select "Previous tag" and choose the previous release tag (e.g.,
website/v1.2.2) - Click "Generate release notes", and review the notes
- Click "Publish release"
The production release workflow is defined in .github/workflows/website_deploy_production.yaml.
Note: Please use SemVer for version numbering.
The website uses Inter for body text and Inter Display for headlines, while other apps in the monorepo continue to use the shared UI library fonts (Roobert and Reckless Neue).
- Font files are loaded via
next/font/localfrom/public/fonts/ - CSS variable overrides in
globals.cssremap the shared UI library font variables
Do not delete any font files from /public/fonts/ - here's why:
- The shared UI library (
/libraries/ui/) defines font URLs pointing tohttps://bluedot.org/fonts/* - 8+ other apps (Storybook, Meet, Room, Editor, etc.) load Roobert/Reckless fonts from these URLs
- Deleting these fonts would break typography in all other apps
- The website overrides fonts locally while serving them for other apps
Legacy fonts (used by other apps via HTTPS):
Roobert-*.woff2- Sans-serif (weights: 300, 400, 600, 700)RecklessNeue-*.woff2- Serif (weights: 300, 300i, 700)
These are loaded by the shared UI library (libraries/ui/src/default-config/tailwind.css) and consumed by the other apps in this monorepo:
storybook— component documentationmeet— meeting attendance + Zoom Web SDKroom— meeting/collaboration appeditor— content editoravailability— scheduling/availability formscourse-demos— interactive course demosfrontend-example— example appapp-template— template for new apps
Website-specific fonts (loaded via next/font):
Inter-*.woff2- Body text (weights: 400, 500, 600, 700)InterDisplay-*.woff2- Headlines (weights: 400, 500, 600, 700)Inter-OFL.txt- SIL Open Font License for Inter fonts
For complete font licensing information, see /public/fonts/README.md
- Ask in the #website-dev Slack channel
- Review the Next.js documentation
- Review the Vitest documentation
- Review the Tailwind CSS documentation