A high-performance Astro 5 blog written in TypeScript that uses Notion's Public API as a headless CMS. Features static site generation, React islands for interactivity, optimized images, and comprehensive SEO with sitemap, RSS, JSON Feed and Schema.org structured data.
Live Site: https://brennanmoore.com
- Static Site Generation: 34 pages pre-rendered at build time for instant loading
- Islands Architecture: React components only where needed (particles, mobile menu)
- Zero JS by Default: Content pages ship no JavaScript except for interactive islands
- Cloudflare Pages: Deployed to Cloudflare's edge network for global performance
- Multi-Format Feeds:
- RSS 2.0 feed (
/rss.xml) for traditional feed readers - JSON Feed 1.1 (
/feed.json) with full content and word counts - Auto-discovery links in HTML for easy subscription
- RSS 2.0 feed (
- Schema.org JSON-LD: Rich structured data with Person, Blog, BlogPosting, and Photograph types
- Dynamic Sitemap: Auto-generated with
@astrojs/sitemap - Social Metadata: OpenGraph and Twitter Card support
- 132 Tests: Comprehensive tests with Vitest and React Testing Library
- Type Safety: Strict TypeScript with full type coverage
- Security: Content Security Policy (CSP) and secure headers via
_headers - Code Quality: ESLint with Astro plugin, Prettier with Astro support
- Notion as CMS: Easy content management through Notion's intuitive interface
- Content Collections: Astro's content layer with custom Notion loader
- Dual Content Types: Separate collections for blog posts and photo galleries
- Series Support: VBC (Value-Based Care) series with navigation
| Category | Technology | Version | Purpose |
|---|---|---|---|
| Framework | Astro | 5.16.11 | Static site generation with islands |
| UI | React | 19.2.3 | Interactive island components |
| Language | TypeScript | 5.9.3 | Type-safe development |
| Styling | Tailwind CSS | 4.1.18 | Utility-first CSS (v4 with Vite plugin) |
| CMS | Notion API | 5.7.0 | Content management |
| Testing | Vitest | 4.0.17 | Fast unit testing |
| Testing | React Testing Library | Latest | Component testing |
| Code Quality | ESLint | 9.39.2 | Code linting with Astro plugin |
| Deployment | Cloudflare Pages | N/A | Edge deployment |
Runtime: Node.js 24.x
brennanmoore-astro-blog/
├── src/
│ ├── pages/ # Astro pages
│ │ ├── index.astro # Homepage
│ │ ├── writing/[slug].astro # Blog post pages
│ │ ├── photos/[slug].astro # Photo gallery pages
│ │ ├── feed.json.ts # JSON Feed 1.1 endpoint
│ │ └── rss.xml.ts # RSS 2.0 feed endpoint
│ ├── layouts/ # Astro layouts
│ │ ├── BaseLayout.astro # HTML shell with head/body
│ │ └── PostLayout.astro # Blog post layout
│ ├── components/ # UI components
│ │ ├── *.astro # Static Astro components
│ │ ├── islands/ # React island components
│ │ │ ├── FloatingParticles.tsx
│ │ │ ├── Header.tsx
│ │ │ └── ContentRenderer.tsx
│ │ └── ui/ # Shared UI components
│ ├── content/ # Content Collections
│ │ └── config.ts # Collection schemas & Notion loader
│ ├── lib/ # Utilities
│ │ ├── notion-loader.ts # Custom Notion content loader
│ │ ├── config.ts # Site configuration
│ │ └── utils.ts # Shared utilities
│ ├── hooks/ # React hooks (for islands)
│ └── styles/
│ └── globals.css # Global styles & Tailwind
├── __tests__/ # Vitest tests (306 tests)
├── public/
│ ├── images/ # Static images
│ ├── _headers # Cloudflare security headers
│ └── _redirects # Cloudflare redirects
├── astro.config.mjs # Astro configuration
├── eslint.config.mjs # ESLint flat config with Astro
├── CLAUDE.md # AI context & patterns
└── README.md # This file
Follow the Notion API Getting Started Guide to create an integration and obtain:
NOTION_TOKEN: Your Notion integration token (starts withsecret_)NOTION_DATA_SOURCE_ID: Data source ID for blog postsNOTION_PHOTOS_DATA_SOURCE_ID: Data source ID for photo gallerySITE_URL: Your site URL for absolute links (e.g.,https://brennanmoore.com)
Create a .env file in the project root:
NOTION_TOKEN=secret_your-notion-integration-token
NOTION_DATA_SOURCE_ID=abc123def456
NOTION_PHOTOS_DATA_SOURCE_ID=xyz789abc123
SITE_URL=https://brennanmoore.comnpm installSystem Requirements: Node.js 24.x
npm run devOpen http://localhost:4321 to view your blog.
Dev Features:
- Hot Module Replacement with Vite
- Content syncs from Notion on startup
- TypeScript type checking
npm run build # Build static site
npm run preview # Preview production build locallyBuild Output: 34 static pages generated in the dist/ directory.
This project is configured for deployment on Cloudflare Pages:
- Push your code to a Git repository (GitHub, GitLab, etc.)
- Log into the Cloudflare dashboard and go to Workers & Pages
- Click Create > Pages > Connect your repository
- Configure build settings:
- Framework preset: Astro
- Build command:
npm run build - Build output directory:
dist
- Add environment variables in the Cloudflare dashboard
- Deploy!
Security headers are configured in public/_headers:
- Content Security Policy (CSP)
- Strict Transport Security (HSTS)
- X-Frame-Options, X-Content-Type-Options
- Cache headers for images and feeds
The project has 132 tests using Vitest and React Testing Library.
npm test # Run all tests
npm run test:watch # Watch mode for development
npm run test:ui # Open Vitest UI| Category | Tests |
|---|---|
| UI Components | Table component tests |
| Utilities | config, errors, toc, notion, download-image, page-utils |
| Hooks | use-mobile |
See CLAUDE.md for testing patterns and conventions.
- Colors & Typography: Edit CSS custom properties in
src/styles/globals.css - Layout: Modify Astro components in
src/layouts/andsrc/components/
- Notion Databases: Update your Notion pages to add posts or photos
- Collections: Modify
src/content/config.tsfor collection schemas - Loader: Customize
src/lib/notion-loader.tsfor content processing
- Sitemap: Configured via
@astrojs/sitemapinastro.config.mjs - RSS Feed: Customize
src/pages/rss.xml.ts - JSON Feed: Modify
src/pages/feed.json.ts
| Script | Description |
|---|---|
npm run dev |
Start development server |
npm run build |
Build for production |
npm run preview |
Preview production build |
npm run check |
Run Astro type checking |
npm test |
Run tests |
npm run test:coverage |
Run tests with coverage |
npm run lint |
Run ESLint |
npm run format |
Format with Prettier |
npm run typecheck |
TypeScript type checking |
Content Not Loading
- Ensure Notion integration has access to your databases
- Check that environment variables are set correctly
- Run
npm run buildto sync content from Notion
Build Failures
- Ensure Node.js version is 24.x
- Clear Astro cache:
rm -rf .astro dist && npm run build
Hydration Issues
- Check for mismatches between server and client rendering
- Ensure React islands use
client:directives correctly
See CLAUDE.md for more troubleshooting tips.
This project is licensed under the MIT License.
- Built with Astro
- Powered by Notion's Public API
- Deployed on Cloudflare Pages
- Testing with Vitest and React Testing Library
Made with Astro and Notion