Skip to content
helmcodePublic

About

NaN Community Landing

Resources

Contributing

Stars

14 stars

Watchers

0 watching

Forks

Repository files navigation

NaN Website

Marketing site and public documentation for nan.builders. Built with Astro 6 (SSR mode) on Cloudflare Workers, vanilla scripts (and a few Preact islands on the events pages), Tailwind CSS v4, and an EN/ES i18n catalog. Signup is open and lives on cloud.nan.builders: the site links there (hero, nav, pricing, footer), and every product unlocks with a plan.

Tech stack

  • Astro 6 (output: 'server', Cloudflare adapter)
  • Preact 10 for the few client-side islands (events)
  • TypeScript (strict)
  • Tailwind CSS v4 (Vite plugin)
  • rehype-pretty-code + Shiki for code highlighting in docs
  • astro-i18n plus a local helper (src/lib/i18n.ts) for ES/EN strings
  • Vitest 4 for unit tests
  • Cloudflare Workers (assets binding + SSR entrypoint)
  • Node.js >=22.12.0, npm (lockfile is package-lock.json)

Architecture

The site is a single Astro app deployed as a Cloudflare Worker. The landing (/, /es), the community page, events, projects and the docs (/docs/*) are SSR-rendered at the edge. There is no signup form or route on the site: the hero's Create account button links to https://cloud.nan.builders/signup (carrying a valid ?plan= from the landing URL; the legacy ?premium=1 maps to plan=premium), Log in links to https://cloud.nan.builders/, and pricing cards link to signup?plan=<product>. Links are built in src/lib/signup.ts. Static assets are served from the ASSETS binding configured in wrangler.jsonc.

See ARCHITECTURE.md for the full breakdown of rendering modes, route inventory, security model, and member lifecycle.

Project structure

website/
├── src/
│   ├── pages/                       # Landing, community, legal, events, projects, docs (EN root, ES under /es)
│   │   └── api/                     # auth/login-request, events proxy, docs manifest
│   ├── components/                  # nan/ (shell + home sections), docs/, events/
│   ├── scripts/                     # Vanilla client scripts (reveal, dither, ...)
│   ├── layouts/                     # NanBase, NanPage, Docs, NanLegal
│   ├── lib/                         # signup links, i18n, docs, events, rate limits
│   ├── styles/                      # Tokens, system CSS, Tailwind theme
│   ├── tests/                       # Vitest suites
│   └── env.d.ts                     # Cloudflare env typings
├── i18n/                            # en.json, es.json translation catalogs
├── public/                          # Static assets, favicons, _routes.json
├── .github/workflows/               # ci.yml, deploy.yml
├── astro.config.mjs
├── wrangler.jsonc
├── vitest.config.ts
├── tsconfig.json
└── package.json

Getting started

Prerequisites

  • Node.js >=22.12.0 (uses native --env-file= in scripts)
  • npm (the lockfile is package-lock.json; do not switch package managers)
  • Access to a cloud-api instance (default: https://cloud-api.nan.builders) for the events pages. Without CLOUD_API_URL the events pages fail to load, but the landing and docs render fine.

Setup

git clone <repo>
cd website
npm install
cp .env.example .env
# fill in values (see "Environment variables" below)
npm run dev

The dev server runs on http://localhost:4321.

Scripts

Script Command Description
dev astro dev Start the Astro dev server with HMR.
build astro check --quiet || true && astro build && cp public/_routes.json dist/client/ Type-check (non-blocking), build for Cloudflare Workers, then copy the routing rules into dist/client/.
preview astro preview Preview the production build locally.
astro astro Run the Astro CLI passthrough.
generate-types wrangler types Regenerate worker-configuration.d.ts from wrangler.jsonc (file is gitignored).
test vitest run Run the full Vitest suite once.
test:watch vitest Run Vitest in watch mode.

Environment variables

Runtime variables are declared in src/env.d.ts and consumed via Astro.locals.runtime.env (Cloudflare adapter). Local development reads them from .env; production values live in the Cloudflare dashboard (set with wrangler secret put).

Name Required Where it's used Description
CLOUD_API_URL runtime src/lib/events.ts, src/pages/api/events/[...path].ts, src/pages/api/auth/login-request.ts Base URL of the cloud-api backend (e.g. https://cloud-api.nan.builders).
RATE_LIMIT_RPM / RATE_LIMIT_PARALLEL optional src/lib/rateLimits.ts Per-key limits shown in the docs; defaults apply when unset.

RESEND_API_KEY, RESEND_FROM_EMAIL and CLOUD_API_WAITLIST_KEY are no longer read by the site and can be removed from the Worker. None of these values should ever be committed.

Testing

npm test         # run all Vitest suites
npm run test:watch

Vitest config: vitest.config.ts (a unit project for src/**/*.test.ts and a render project for src/**/*.render.test.ts). The signup-link suites are src/tests/lib/signup.test.ts, src/tests/landing/heroCta.test.ts, src/tests/landing/heroCta.server.test.ts and src/tests/landing/openSignup.test.ts.

Build and local preview

npm run build    # astro check (non-blocking) → astro build → copy _routes.json
npm run preview  # serve the production build locally

astro check is invoked with --quiet and || true so type warnings do not fail the build; full type checking happens in CI via npx astro check.

Deployment

The site is deployed to Cloudflare Workers under the name nan-website (see wrangler.jsonc). Pushes to main trigger the Deploy workflow, which runs tests, builds, and then npx wrangler deploy. The production site is served at https://nan.builders.

wrangler.jsonc highlights:

  • main: "@astrojs/cloudflare/entrypoints/server" — Astro's Cloudflare SSR entrypoint.
  • assets: { directory: "./dist", binding: "ASSETS" } — static assets binding for the Worker.
  • compatibility_date: "2026-03-17" and compatibility_flags: ["global_fetch_strictly_public"] — outbound fetch is restricted to public addresses.
  • observability.enabled: true — Workers observability is on.

All runtime secrets must be set in the Cloudflare dashboard (or via wrangler secret put), not in the repository.

Rollback

Because every wrangler deploy publishes a numbered Version, Cloudflare keeps the history and a rollback needs no rebuild. It is a code-and-assets rollback only: it does not revert secrets, vars, or anything living in cloud-api.

Before merging anything large, write down the version that is live:

npx wrangler deployments status     # shows the Version ID currently serving
npx wrangler versions list          # the 10 most recent Versions

To roll back:

npx wrangler rollback <version-id>  # omit the id to pick the previous one
npx wrangler deployments status     # confirm what is serving now

Give it a minute and re-check the site: the Worker flips immediately, but a CDN-cached asset can outlive the rollback for a short while.

Previewing a release before it serves traffic

wrangler deploy sends 100% of traffic to the new Version straight away. To see a build on real Cloudflare infrastructure first, upload a Version without deploying it. This is worth doing for releases that replace many pages at once:

npm run build
npx wrangler versions upload         # prints a preview URL, serves no traffic

Once it looks right, either merge as usual or promote that exact Version:

npx wrangler versions deploy <version-id>@100

wrangler versions deploy also splits traffic (for example <id>@10 for 10%), which is the safer path for a change that touches routing or URLs.

CI/CD

Two GitHub Actions workflows live in .github/workflows/:

ci.yml — CI

  • Trigger: pull_request to main.
  • Job build-and-test (Ubuntu, Node 22, npm cache):
    1. npm ci
    2. npm test
    3. npx astro check
    4. npm run build

deploy.yml — Deploy

  • Trigger: push to main.
  • Job deploy (Ubuntu, Node 22, npm cache, deployments: write):
    1. npm ci
    2. npm test
    3. npm run build
    4. npx wrangler deploy

Required repository secrets (consumed by deploy.yml):

  • CLOUDFLARE_API_TOKEN
  • CLOUDFLARE_ACCOUNT_ID

Internationalization

The site supports Spanish (es, default) and English (en).

To add a new string, add the key (matching the dotted path used in t(...)) to both i18n/es.json and i18n/en.json, then reference it from the component with t('your.key', locale). Both catalogs must stay in structural sync.

Contributing

  • Branch from main and open a PR.
  • Commit messages follow Conventional Commits (feat(scope): ..., fix(scope): ..., docs(scope): ...). Match the existing git log style.
  • CI must be green: npm test, npx astro check, and npm run build all pass.
  • For UI changes, run npm run dev and smoke-test the affected flows before requesting review.
  • Never commit secrets or any .env file.

About

NaN Community Landing

Resources

Contributing

Stars

14 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages