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 cover
Before you begin, ensure you have the following installed:
-
Node.js version 22
- Download from nodejs.org
- Verify installation:
node --version(should show v22.12+)
-
npm
- Comes bundled with Node.js
- Verify installation:
npm --version
This website uses modern web technologies. Here is what each one does (with Java analogies):
- React Router - full-stack web framework with SSG
- Handles routing (similar to Spring MVC controllers)
- Provides server-side rendering for better performance and SEO
- Enables progressive enhancement
- Documentation
- Fumadocs - documentation framework used for the docs section
- Provides MDX-based docs structure and navigation
- Lives alongside landing pages in the same React Router app
- Supports multi-page and single-page docs from the same MDX sources
- Documentation
The website uses progressive enhancement (learn more), which means:
-
With JavaScript enabled: users get a SPA experience
- Fast page transitions without full page reloads
- Smooth animations and interactive features
- Enhanced user experience
-
Without JavaScript: users still get a fully functional website
- All links and forms work via traditional HTML
- Content remains accessible
- Better behavior for search engines and assistive tools
This approach ensures the website works for all users, regardless of browser capabilities or connection speed.
- shadcn/ui - pre-built, accessible UI components
- Similar to component libraries like PrimeFaces or Vaadin
- Provides buttons, cards, navigation menus, and more
- Documentation
- TailwindCSS - utility-first CSS framework
- Apply classes directly in components instead of maintaining large CSS files
- Example:
className="text-blue-500 font-bold"makes blue, bold text
-
TypeScript - typed superset of JavaScript
- Similar to Java's type system
- Catches errors at compile-time instead of runtime
- Provides better autocomplete and IDE support
-
ESLint + Prettier - linting and formatting
- ESLint analyzes code for potential errors and style issues
- Prettier enforces consistent formatting
npm run lint:fixhandles both linting and markdown formatting- Configuration files:
eslint.config.jsandprettier.config.js
The 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
- UI components (
app/ui) are pure, reusable building blocks with no page-level business logic. - Business components (
app/components) can be shared across multiple pages. - Pages (
app/pages) compose UI + business components into complete routes. - Routes (
app/routes) map URLs to pages and define route-level metadata. - Two layout systems exist in one app:
- Landing pages under
app/pages/_landing/ - Docs pages under
app/pages/_docs/
- Landing pages under
- Documentation versions:
- Multi-page docs in
app/pages/_docs/docs/_mdx/(multi-page)/are the source of truth. - Single-page docs in
app/pages/_docs/docs/_mdx/single-page/aggregate from multi-page docs.
- Multi-page docs in
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 installThis 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-initgenerate-language updates generated docs pages (for example grammar/functions/datatypes). fumadocs-init refreshes Fumadocs metadata and page maps.
npm run devThis starts a local development server with:
- Hot Module Replacement (HMR): updates without full page reload
- Default URL:
http://localhost:5173
- Edit code in
app/. - Save the file and verify updates in browser.
- Check terminal output and browser console for errors.
Add a new page:
- Create directory in
app/pages/my-new-page/ - Add
index.tsxin that directory - Add a route file in
app/routes/ - Register it in
app/routes.ts
Add a new documentation page:
- Create a
.mdxfile inapp/pages/_docs/docs/_mdx/(multi-page)/. - Add it to the relevant
meta.jsonin that docs section. - If needed in the single-page docs, include it from
app/pages/_docs/docs/_mdx/single-page/index.mdx. - Run
npm run fumadocs-init.
Update content:
- Edit the relevant
.md,.mdx, or.jsonfile.
Add a UI component:
- Check existing shadcn/ui primitives first.
- Add custom components only when needed.
Update the 404 page:
- Edit the content in
app/routes/404.tsx. - Apache 404 handling lives in
public/.htaccess(usesErrorDocument 404 /404).
Check code quality:
npm run lintFix linting and formatting issues:
npm run lint:fixThe 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:uiDuring local development, running CI checks is usually enough:
npm run ciBefore opening a pull request, you must run the full website build script:
./build.shbuild.sh is the required pre-PR build step for all contributors (not just Linux users). It:
- Ensures Node/npm are available (bootstraps via
nvmwhen needed) - Runs a clean dependency install (
npm ci) - Runs the complete CI pipeline (
npm run ci) - Copies
build/client/intooutput/
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:e2e
There 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:
- Run
./build.shlocally - Commit source changes and updated
output/ - Push your branch and open a PR
After merge, output/ can be deployed to static hosting.
The output/ content (generated from build/client/) can be served from any static host:
- Apache HTTP Server
- Nginx
- GitHub Pages
- Any CDN/static object storage
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 typegenIf npm run dev fails because port 5173 is in use:
lsof -ti:5173 | xargs kill -9Or 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
- React Router: https://reactrouter.com/
- Fumadocs: https://fumadocs.com/
- Progressive Enhancement: https://reactrouter.com/explanation/progressive-enhancement
- shadcn/ui: https://ui.shadcn.com/
- TailwindCSS: https://tailwindcss.com/
- TypeScript Handbook: https://www.typescriptlang.org/docs/
Built for the Apache Phoenix community.