A modern Next.js website for Builder Vancouver - a community-driven Bitcoin meetup focused on education, Lightning Network development, and Layer 2 exploration.
Bitcoin Builder Vancouver is a content-driven website built with Next.js 16, featuring:
- Type-Safe Content Management: JSON-based content with Zod schema validation
- SEO-Optimized: Comprehensive metadata and Schema.org structured data
- Server Components: Leveraging Next.js App Router for optimal performance
- Modern Stack: TypeScript, Tailwind CSS, and React Server Components
The site uses a JSON-based content management system with runtime validation:
- Content Files: All content stored in
/contentdirectory as JSON - Zod Schemas: Type-safe validation schemas in
/lib/schemas.ts - Type Inference: TypeScript types automatically inferred from schemas
- Content Loaders: Type-safe functions to load and validate content
Example content flow:
content/events.json → Zod Schema Validation → TypeScript Types → React Components
bitcoin-builder/
├── app/ # Next.js app directory (routes)
│ ├── events/ # Events pages
│ ├── recaps/ # Event recaps
│ ├── about/ # About pages (mission, vision, etc.)
│ └── ...
├── components/ # React components
│ ├── layout/ # Layout components (Navbar, PageContainer)
│ ├── seo/ # SEO components (JsonLd)
│ └── ui/ # UI components (Heading, Section)
├── content/ # JSON content files
│ ├── events.json
│ ├── recaps.json
│ └── ...
├── lib/ # Core utilities and helpers
│ ├── content.ts # Content loaders
│ ├── schemas.ts # Zod schemas
│ ├── types.ts # TypeScript types
│ ├── seo.ts # SEO utilities
│ └── structured-data.ts # Schema.org JSON-LD builders
└── public/ # Static assets
- Next.js 16: App Router with React Server Components
- TypeScript: Full type safety across the codebase
- Zod: Runtime type validation for content
- Tailwind CSS: Utility-first styling
- Schema.org: Rich structured data for SEO and AI agents
- Node.js 20+ (recommended)
- pnpm (recommended), npm, or yarn
# Clone the repository
git clone <repository-url>
cd bitcoin-builder
# Install dependencies
pnpm install# Start the development server
pnpm dev
# Open http://localhost:3000 in your browserThe development server includes:
- Hot module replacement
- Fast refresh for instant updates
- Type checking in your IDE
# Type check
pnpm tsc
# Build for production
pnpm build
# Start production server
pnpm start# Format code with Prettier
pnpm format
# Check formatting without changes
pnpm format:check
# Run ESLint
pnpm lint
# Fix ESLint issues automatically
pnpm lint:fix
# Validate content files
pnpm validate:content
# Run all checks (validation + type checking)
pnpm content:checkPre-commit Hooks: The project uses Husky and lint-staged to automatically format, lint, and validate code before commits. See Formatting & Linting Guide for details.
- Open
content/events.json - Add a new event object to the
eventsarray:
{
"title": "Your Event Title",
"slug": "your-event-slug",
"date": "2025-12-15",
"time": "6:00 PM - 8:30 PM",
"location": "Venue Name, Vancouver",
"description": "Brief description of the event",
"sections": [
{
"title": "Section Title",
"body": "Section content"
}
],
"meta": {
"title": "Your Event Title | Builder Vancouver",
"description": "SEO description",
"keywords": ["bitcoin", "event", "vancouver"]
}
}- The event will automatically appear on
/eventsand be accessible at/events/your-event-slug
All content is validated against Zod schemas at runtime. If content doesn't match the schema, you'll see detailed validation errors in the console during development.
Schema Validation Ensures:
- Required fields are present
- Data types are correct
- URLs are properly formatted
- Dates follow expected format
The site supports several content types:
- Events: Meetups, workshops, and community events
- Recaps: Summaries of past events
- Educational Content: Bitcoin 101, Lightning 101, Layer 2 guides
- Resources: Curated links and learning materials
- Projects: Community projects and initiatives
- Foundation Pages: Mission, Vision, Charter, Philosophy
Each content type has its own schema and validation rules defined in /lib/schemas.ts.
Every page generates comprehensive metadata using Next.js Metadata API:
export const metadata = generatePageMetadata("Page Title", "Page description", [
"keyword1",
"keyword2",
]);Pages include Schema.org structured data for enhanced SEO and AI agent understanding:
import { createEventSchema, JsonLd } from "@/lib/seo";
// In your component
const eventSchema = createEventSchema(event);
return <JsonLd data={eventSchema} />;Available Schema Builders:
createOrganizationSchema()- Organization infocreateWebSiteSchema()- Website metadatacreateEventSchema()- EventscreateArticleSchema()- Recaps/articlescreateCourseSchema()- Educational contentcreateBreadcrumbList()- Navigation breadcrumbs
Most components are Server Components that fetch data directly:
export default function EventsPage() {
const { events } = loadEvents(); // Direct data loading
return <div>{/* render events */}</div>;
}Following project conventions, all components and functions use named exports:
// Good
export function MyComponent() { }
// Avoid
export default function MyComponent() { }When using dynamic routes, always await params:
export default async function EventDetailPage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params; // Always await!
const event = loadEvent(slug);
// ...
}The site uses Tailwind CSS with a Bitcoin-themed color scheme:
- Primary:
orange-400(Bitcoin orange) - Background:
neutral-950(dark) - Text:
neutral-100(light) - Borders:
neutral-800
Common utilities are available in /components/ui:
<Heading>- Semantic headings with consistent styling<Section>- Content sections with proper spacing<PageContainer>- Main page wrapper with responsive padding
- Use TypeScript for all new files
- Follow existing naming conventions
- Use named exports consistently
- Add proper TypeScript types (no
any) - Use functional components with hooks
Follow conventional commit methodology:
feat: Add new event page
fix: Resolve event date formatting
docs: Update README with new content type
refactor: Reorganize content loaders
- Run type check:
pnpm tsc - Run build:
pnpm build - Verify changes in dev mode
- Test on multiple pages
If you see validation errors:
- Check the error message for specific field issues
- Refer to the schema in
/lib/schemas.ts - Ensure all required fields are present
- Verify data types match schema expectations
If build fails:
- Run
pnpm tscto check for type errors - Clear Next.js cache:
rm -rf .next - Reinstall dependencies:
rm -rf node_modules && pnpm install
All imports use path aliases:
@/lib/*- Library utilities@/components/*- React components@/app/*- App routes
Ensure tsconfig.json paths are properly configured.
This site is optimized for deployment on Vercel:
# Deploy to production
vercel --prodRequired environment variables:
NEXT_PUBLIC_SITE_URL=https://bitcoinbuidlr.xyzOptional environment variables for social media cross-posting:
# X (Twitter) API Credentials
# Get these from https://developer.twitter.com/en/portal/dashboard
X_API_KEY=your_api_key
X_API_SECRET=your_api_secret
X_ACCESS_TOKEN=your_access_token
X_ACCESS_TOKEN_SECRET=your_access_token_secret
X_BEARER_TOKEN=your_bearer_token
# Nostr Configuration
# Generate a private key using a Nostr client or tool
NOSTR_PRIVATE_KEY=your_hex_encoded_private_key
# JSON array of relay URLs (defaults to ["wss://relay.damus.io"] if not set)
NOSTR_RELAYS=["wss://relay.damus.io","wss://nos.lol"]Note: These are optional and only needed if you want to use the social media posting features. The API route will gracefully handle missing credentials.
The /examples directory contains annotated example files demonstrating best practices for the Bitcoin Builder Vancouver codebase.
Complete page component implementation showing:
- Next.js metadata generation
- Schema.org structured data (JSON-LD)
- Breadcrumb navigation
- Type-safe content loading
- URL builder usage
- Proper component structure
Use this as a template when creating new static pages.
Dynamic route handling with [slug] parameters:
- Async params handling (Next.js 15+ requirement)
- 404 error handling with
notFound() - Static path generation for SSG
- Dynamic metadata generation
- Type-safe props interface
Use this as a template when creating new dynamic routes.
Annotated JSON content file showing:
- All available schema fields
- Proper formatting and structure
- Comments explaining each field
- Examples of sections, links, images, and metadata
Use this as a reference when creating or editing content files.
- Creating a New Page: Copy
example-page-with-seo.tsxand adapt it to your needs - Creating a Dynamic Route: Copy
example-dynamic-route.tsxfor [slug] routes - Understanding Content Structure: Refer to
example-content-file.json
These examples serve as reference implementations. When asked to:
- Create a page: Use
example-page-with-seo.tsxas the baseline pattern - Add a dynamic route: Follow patterns in
example-dynamic-route.tsx - Structure content: Match the format in
example-content-file.json
All examples follow these core principles:
- Named Exports: Always use named exports, never default exports
- Type Safety: Use TypeScript types from
@/lib/types - URL Builders: Never hardcode URLs, use
@/lib/utils/urls - Content Loaders: Use type-safe loaders from
@/lib/content - SEO First: Every page needs metadata and structured data
- Semantic HTML: Proper heading hierarchy (H1 → H2 → H3)
- Async Params: Always await params in Next.js 15+
// Static content
const content = loadBitcoin101();
// Collection
const { events } = loadEvents();
// Single item from collection
const event = loadEvent(slug);// For structured data (full URLs)
urls.events.list(); // "https://bitcoinbuidlr.xyz/events"
urls.events.detail(slug); // "https://bitcoinbuidlr.xyz/events/lightning-workshop"
// For Next.js Link (paths only)
paths.events.list(); // "/events"
paths.events.detail(slug); // "/events/lightning-workshop"// Choose appropriate schema type
const eventSchema = createEventSchema(event);
const articleSchema = createArticleSchema(recap);
const courseSchema = createCourseSchema(course);
// Add breadcrumbs
const breadcrumbs = createBreadcrumbList([
{ name: "Home", url: urls.home() },
{ name: "Events", url: urls.events.list() },
{ name: "Current Page" },
]);
// Combine into graph
const structuredData = createSchemaGraph(eventSchema, breadcrumbs);After creating a new page based on these examples:
- Run type check:
pnpm tsc - Validate content:
pnpm validate:content - Build the site:
pnpm build - Check in browser: Test all links and functionality
- Verify SEO: Check JSON-LD in page source
Comprehensive guides for working with the codebase:
- Architecture Documentation - System architecture, patterns, and design decisions
- Schema Development Guide - Creating and managing content schemas
- Content Authoring Guide - Writing and editing content
- Formatting & Linting Guide - Code quality and standards
- New to the project? Start with Architecture Documentation
- Adding content? See Content Authoring Guide
- Creating new schemas? Read Schema Development Guide
- Code quality? Review Formatting & Linting Guide
- Need examples? Check the
/examplesdirectory for reference implementations
[License information here]
Builder Vancouver
- Website: https://bitcoinbuidlr.xyz
- [Add social media links]
Built with ⚡ by the Builder Vancouver community