The official website for Apache Phoenix, built with modern web technologies to provide a fast, accessible, and maintainable web presence.
Most landing pages store content in Markdown (.md) or JSON (.json) files located in app/pages/_landing/[page-name]/. Docs content lives under app/pages/_docs/ and is authored in MDX.
Examples:
app/pages/_landing/mailing-lists/content.md - Markdown content for a landing pageapp/pages/_landing/team/developers.json - JSON data for the team pageapp/pages/_landing/news/events.json - JSON data for news/eventsapp/pages/_docs/docs/_mdx/(multi-page)/... - MDX content for documentationphoenix-version.ts - Shared Phoenix version constant used in docs/PDF coverBefore you begin, ensure you have the following installed:
Node.js version 22
node --version (should show v22.12+)npm
npm --versionThis website uses modern web technologies. Here is what each one does (with Java analogies):
The website uses progressive enhancement (learn more), which means:
With JavaScript enabled: users get a SPA experience
Without JavaScript: users still get a fully functional website
This approach ensures the website works for all users, regardless of browser capabilities or connection speed.
className="text-blue-500 font-bold" makes blue, bold textTypeScript - typed superset of JavaScript
ESLint + Prettier - linting and formatting
npm run lint:fix handles both linting and markdown formattingeslint.config.js and prettier.config.jsThe project follows a clear directory structure with separation of concerns:
phoenix-site/ ├── app/ # Application source code │ ├── ui/ # Reusable UI primitives │ ├── components/ # Reusable components with business logic │ ├── pages/ # Full pages │ │ ├── _landing/ # Landing pages + layout │ │ └── _docs/ # Documentation pages (Fumadocs) │ ├── routes/ # Route definitions │ ├── routes.ts # Main routing configuration │ ├── root.tsx # Root layout component │ └── app.css # Global styles │ ├── build/ # Generated files (do not edit) ├── output/ # Committed website artifact for publishing ├── public/ # Static files copied to build output ├── scripts/ # Helper scripts (e.g. generate-language.ts) ├── e2e-tests/ # Playwright tests ├── unit-tests/ # Vitest tests ├── phoenix-version.ts # Shared Phoenix version constant └── package.json # Scripts and dependencies
app/ui) are pure, reusable building blocks with no page-level business logic.app/components) can be shared across multiple pages.app/pages) compose UI + business components into complete routes.app/routes) map URLs to pages and define route-level metadata.app/pages/_landing/app/pages/_docs/app/pages/_docs/docs/_mdx/(multi-page)/ are the source of truth.app/pages/_docs/docs/_mdx/single-page/ aggregate from multi-page docs.Always use the custom Link component from @/components/link instead of importing Link directly from react-router.
The Phoenix website includes both React-routed pages and static/legacy pages. The custom Link component automatically decides whether to use client-side navigation or trigger a full page load.
Correct usage:
import { Link } from "@/components/link"; export const MyComponent = () => <Link to="/team">Team</Link>;
Wrong usage:
import { Link } from "react-router"; export const MyComponent = () => <Link to="/team">Team</Link>;
The ESLint configuration includes custom/no-react-router-link to enforce this convention.
npm install
This downloads all required packages from npm.
Before starting the development server, generate docs metadata and generated language pages:
npm run generate-language npm run fumadocs-init
generate-language updates generated docs pages (for example grammar/functions/datatypes). fumadocs-init refreshes Fumadocs metadata and page maps.
npm run dev
This starts a local development server with:
http://localhost:5173app/.Add a new page:
app/pages/my-new-page/index.tsx in that directoryapp/routes/app/routes.tsAdd a new documentation page:
.mdx file in app/pages/_docs/docs/_mdx/(multi-page)/.meta.json in that docs section.app/pages/_docs/docs/_mdx/single-page/index.mdx.npm run fumadocs-init.Update content:
.md, .mdx, or .json file.Add a UI component:
Update the 404 page:
app/routes/404.tsx.public/.htaccess (uses ErrorDocument 404 /404).Check code quality:
npm run lint
Fix linting and formatting issues:
npm run lint:fix
The project uses Vitest for unit testing and Playwright for e2e testing.
Playwright runs against the production build by serving build/client/ locally on port 5178.
The docs PDF export is implemented as a Playwright e2e test in e2e-tests/export-pdf.spec.ts. It renders single-page docs in both light and dark themes to produce static PDF assets.
The export quality depends heavily on print styles in app/app.css (@media print rules).
The displayed Phoenix version on the PDF cover is sourced from phoenix-version.ts (PHOENIX_VERSION) and consumed by app/pages/_docs/docs/index.tsx.
Manual command:
npm run export-pdf
# Run all tests npm test # Run unit tests once (CI mode) npm run test:unit:run # Run unit tests with UI npm run test:unit:ui # Run e2e tests npm run test:e2e # Run e2e tests with UI npm run test:e2e:ui
During local development, running CI checks is usually enough:
npm run ci
Before opening a pull request, you must run the full website build script:
./build.sh
build.sh is the required pre-PR build step for all contributors (not just Linux users). It:
nvm when needed)npm ci)npm run ci)build/client/ into output/Current CI sequence used by npm run ci:
npm run generate-languagenpm run fumadocs-initnpm run lintnpm run typechecknpm run buildnpm run test:unit:runnpx playwright installnpm run test:e2eThere is currently no remote CI/CD runner executing build.sh for this repository. The output/ artifact is produced locally and must be included in your pull request.
The published website artifact is the committed output/ directory. The expected publishing workflow is:
./build.sh locallyoutput/After merge, output/ can be deployed to static hosting.
The output/ content (generated from build/client/) can be served from any static host:
Depending on your machine, it's theoretically possible that the E2E tests can fail due to a timeout. For the E2E tests to start working, it has to warm up the entire application first. The documentation is quite heavy, so it takes time. On slower machines the current timeout (60 secs) might not be sufficient. Therefore, you might want to extend it locally in playwright.config.ts
If you see type errors related to React Router +types, regenerate them:
npx react-router typegen
If npm run dev fails because port 5173 is in use:
lsof -ti:5173 | xargs kill -9
Or change the port in vite.config.ts.
Clear generated files:
rm -rf build/ node_modules/ .vite/ .react-router/ .source/
Reinstall dependencies:
npm install
Try building again:
npm run build
Built for the Apache Phoenix community.