|
1 | 1 | # Notion Blog with Astro |
2 | 2 |
|
3 | | -A high-performance [Astro 5](https://astro.build/) blog written in [TypeScript](https://www.typescriptlang.org/) that uses [Notion's Public API](https://developers.notion.com) 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. |
| 3 | +A personal blog built with [Astro](https://astro.build/) using [Notion](https://developers.notion.com) as a headless CMS. Static site generation with React islands for interactivity. |
4 | 4 |
|
5 | | -**Live Site:** [https://brennanmoore.com](https://brennanmoore.com) |
6 | | - |
7 | | -[]() |
8 | | -[]() |
9 | | -[]() |
10 | | - |
11 | | ---- |
12 | | - |
13 | | -## Key Features |
14 | | - |
15 | | -### Performance & Build |
16 | | -- **Static Site Generation**: 34 pages pre-rendered at build time for instant loading |
17 | | -- **Islands Architecture**: React components only where needed (particles, mobile menu) |
18 | | -- **Zero JS by Default**: Content pages ship no JavaScript except for interactive islands |
19 | | -- **Cloudflare Pages**: Deployed to Cloudflare's edge network for global performance |
20 | | - |
21 | | -### SEO & Discovery |
22 | | -- **Multi-Format Feeds**: |
23 | | - - RSS 2.0 feed (`/rss.xml`) for traditional feed readers |
24 | | - - JSON Feed 1.1 (`/feed.json`) with full content and word counts |
25 | | - - Auto-discovery links in HTML for easy subscription |
26 | | -- **Schema.org JSON-LD**: Rich structured data with Person, Blog, BlogPosting, and Photograph types |
27 | | -- **Dynamic Sitemap**: Auto-generated with `@astrojs/sitemap` |
28 | | -- **Social Metadata**: OpenGraph and Twitter Card support |
29 | | - |
30 | | -### Developer Experience |
31 | | -- **132 Tests**: Comprehensive tests with Vitest and React Testing Library |
32 | | -- **Type Safety**: Strict TypeScript with full type coverage |
33 | | -- **Security**: Content Security Policy (CSP) and secure headers via `_headers` |
34 | | -- **Code Quality**: ESLint with Astro plugin, Prettier with Astro support |
35 | | - |
36 | | -### Content Management |
37 | | -- **Notion as CMS**: Easy content management through Notion's intuitive interface |
38 | | -- **Content Collections**: Astro's content layer with custom Notion loader |
39 | | -- **Dual Content Types**: Separate collections for blog posts and photo galleries |
40 | | -- **Series Support**: VBC (Value-Based Care) series with navigation |
41 | | - |
42 | | ---- |
| 5 | +**Live Site:** [brennanmoore.com](https://brennanmoore.com) |
43 | 6 |
|
44 | 7 | ## Tech Stack |
45 | 8 |
|
46 | | -| Category | Technology | Version | Purpose | |
47 | | -|----------|-----------|---------|---------| |
48 | | -| **Framework** | [Astro](https://astro.build/) | 5.16.11 | Static site generation with islands | |
49 | | -| **UI** | [React](https://react.dev/) | 19.2.3 | Interactive island components | |
50 | | -| **Language** | [TypeScript](https://www.typescriptlang.org/) | 5.9.3 | Type-safe development | |
51 | | -| **Styling** | [Tailwind CSS](https://tailwindcss.com/) | 4.1.18 | Utility-first CSS (v4 with Vite plugin) | |
52 | | -| **CMS** | [Notion API](https://developers.notion.com) | 5.7.0 | Content management | |
53 | | -| **Testing** | [Vitest](https://vitest.dev/) | 4.0.17 | Fast unit testing | |
54 | | -| **Testing** | [React Testing Library](https://testing-library.com/react) | Latest | Component testing | |
55 | | -| **Code Quality** | [ESLint](https://eslint.org/) | 9.39.2 | Code linting with Astro plugin | |
56 | | -| **Deployment** | [Cloudflare Pages](https://pages.cloudflare.com/) | N/A | Edge deployment | |
57 | | - |
58 | | -**Runtime**: Node.js 24.x |
59 | | - |
60 | | ---- |
61 | | - |
62 | | -## Project Structure |
63 | | - |
64 | | -``` |
65 | | -brennanmoore-astro-blog/ |
66 | | -├── src/ |
67 | | -│ ├── pages/ # Astro pages |
68 | | -│ │ ├── index.astro # Homepage |
69 | | -│ │ ├── writing/[slug].astro # Blog post pages |
70 | | -│ │ ├── photos/[slug].astro # Photo gallery pages |
71 | | -│ │ ├── feed.json.ts # JSON Feed 1.1 endpoint |
72 | | -│ │ └── rss.xml.ts # RSS 2.0 feed endpoint |
73 | | -│ ├── layouts/ # Astro layouts |
74 | | -│ │ ├── BaseLayout.astro # HTML shell with head/body |
75 | | -│ │ └── PostLayout.astro # Blog post layout |
76 | | -│ ├── components/ # UI components |
77 | | -│ │ ├── *.astro # Static Astro components |
78 | | -│ │ ├── islands/ # React island components |
79 | | -│ │ │ ├── FloatingParticles.tsx |
80 | | -│ │ │ ├── Header.tsx |
81 | | -│ │ │ └── ContentRenderer.tsx |
82 | | -│ │ └── ui/ # Shared UI components |
83 | | -│ ├── content/ # Content Collections |
84 | | -│ │ └── config.ts # Collection schemas & Notion loader |
85 | | -│ ├── lib/ # Utilities |
86 | | -│ │ ├── notion-loader.ts # Custom Notion content loader |
87 | | -│ │ ├── config.ts # Site configuration |
88 | | -│ │ └── utils.ts # Shared utilities |
89 | | -│ ├── hooks/ # React hooks (for islands) |
90 | | -│ └── styles/ |
91 | | -│ └── globals.css # Global styles & Tailwind |
92 | | -├── __tests__/ # Vitest tests (306 tests) |
93 | | -├── public/ |
94 | | -│ ├── images/ # Static images |
95 | | -│ ├── _headers # Cloudflare security headers |
96 | | -│ └── _redirects # Cloudflare redirects |
97 | | -├── astro.config.mjs # Astro configuration |
98 | | -├── eslint.config.mjs # ESLint flat config with Astro |
99 | | -├── CLAUDE.md # AI context & patterns |
100 | | -└── README.md # This file |
101 | | -``` |
102 | | - |
103 | | ---- |
| 9 | +- **Astro 5** – Static site generation |
| 10 | +- **React 19** – Interactive components (islands) |
| 11 | +- **Tailwind CSS 4** – Styling |
| 12 | +- **Notion API** – Content management |
| 13 | +- **Cloudflare Pages** – Deployment |
104 | 14 |
|
105 | 15 | ## Getting Started |
106 | 16 |
|
107 | | -### 1. Set Up Notion API Credentials |
| 17 | +### Prerequisites |
108 | 18 |
|
109 | | -Follow the [Notion API Getting Started Guide](https://developers.notion.com/docs/getting-started) to create an integration and obtain: |
| 19 | +- Node.js 24.x |
| 20 | +- Notion integration token ([setup guide](https://developers.notion.com/docs/getting-started)) |
110 | 21 |
|
111 | | -- `NOTION_TOKEN`: Your Notion integration token (starts with `secret_`) |
112 | | -- `NOTION_DATA_SOURCE_ID`: Data source ID for blog posts |
113 | | -- `NOTION_PHOTOS_DATA_SOURCE_ID`: Data source ID for photo gallery |
114 | | -- `SITE_URL`: Your site URL for absolute links (e.g., `https://brennanmoore.com`) |
| 22 | +### Environment Variables |
115 | 23 |
|
116 | | -### 2. Configure Environment Variables |
117 | | - |
118 | | -Create a `.env` file in the project root: |
| 24 | +Create a `.env` file: |
119 | 25 |
|
120 | 26 | ```env |
121 | | -NOTION_TOKEN=secret_your-notion-integration-token |
122 | | -NOTION_DATA_SOURCE_ID=abc123def456 |
123 | | -NOTION_PHOTOS_DATA_SOURCE_ID=xyz789abc123 |
124 | | -SITE_URL=https://brennanmoore.com |
| 27 | +NOTION_TOKEN=secret_xxx |
| 28 | +NOTION_DATA_SOURCE_ID=xxx |
| 29 | +NOTION_PHOTOS_DATA_SOURCE_ID=xxx |
| 30 | +SITE_URL=https://your-site.com |
125 | 31 | ``` |
126 | 32 |
|
127 | | -### 3. Install Dependencies |
| 33 | +### Development |
128 | 34 |
|
129 | 35 | ```bash |
130 | 36 | npm install |
| 37 | +npm run dev # Start dev server at localhost:4321 |
131 | 38 | ``` |
132 | 39 |
|
133 | | -**System Requirements**: Node.js 24.x |
134 | | - |
135 | | -### 4. Start Development Server |
136 | | - |
137 | | -```bash |
138 | | -npm run dev |
139 | | -``` |
140 | | - |
141 | | -Open [http://localhost:4321](http://localhost:4321) to view your blog. |
142 | | - |
143 | | -**Dev Features**: |
144 | | -- Hot Module Replacement with Vite |
145 | | -- Content syncs from Notion on startup |
146 | | -- TypeScript type checking |
147 | | - |
148 | | -### 5. Build for Production |
| 40 | +### Production |
149 | 41 |
|
150 | 42 | ```bash |
151 | | -npm run build # Build static site |
152 | | -npm run preview # Preview production build locally |
| 43 | +npm run build # Build static site to dist/ |
| 44 | +npm run preview # Preview build locally |
153 | 45 | ``` |
154 | 46 |
|
155 | | -**Build Output**: 34 static pages generated in the `dist/` directory. |
156 | | - |
157 | | ---- |
158 | | - |
159 | | -## Deployment (Cloudflare Pages) |
160 | | - |
161 | | -This project is configured for deployment on [Cloudflare Pages](https://pages.cloudflare.com/): |
162 | | - |
163 | | -1. Push your code to a Git repository (GitHub, GitLab, etc.) |
164 | | -2. Log into the Cloudflare dashboard and go to **Workers & Pages** |
165 | | -3. Click **Create** > **Pages** > Connect your repository |
166 | | -4. Configure build settings: |
167 | | - - **Framework preset**: Astro |
168 | | - - **Build command**: `npm run build` |
169 | | - - **Build output directory**: `dist` |
170 | | -5. Add environment variables in the Cloudflare dashboard |
171 | | -6. Deploy! |
172 | | - |
173 | | -### Security Headers |
174 | | - |
175 | | -Security headers are configured in `public/_headers`: |
176 | | -- Content Security Policy (CSP) |
177 | | -- Strict Transport Security (HSTS) |
178 | | -- X-Frame-Options, X-Content-Type-Options |
179 | | -- Cache headers for images and feeds |
180 | | - |
181 | | ---- |
182 | | - |
183 | | -## Testing |
184 | | - |
185 | | -The project has **132 tests** using [Vitest](https://vitest.dev/) and React Testing Library. |
186 | | - |
187 | | -### Run Tests |
188 | | - |
189 | | -```bash |
190 | | -npm test # Run all tests |
191 | | -npm run test:watch # Watch mode for development |
192 | | -npm run test:ui # Open Vitest UI |
193 | | -``` |
194 | | - |
195 | | -### Test Coverage |
196 | | - |
197 | | -| Category | Tests | |
198 | | -|----------|-------| |
199 | | -| **UI Components** | Table component tests | |
200 | | -| **Utilities** | config, errors, toc, notion, download-image, page-utils | |
201 | | -| **Hooks** | use-mobile | |
202 | | - |
203 | | -See [CLAUDE.md](CLAUDE.md) for testing patterns and conventions. |
204 | | - |
205 | | ---- |
206 | | - |
207 | | -## Customization |
208 | | - |
209 | | -### Design & Styling |
210 | | -- **Colors & Typography**: Edit CSS custom properties in `src/styles/globals.css` |
211 | | -- **Layout**: Modify Astro components in `src/layouts/` and `src/components/` |
212 | | - |
213 | | -### Content |
214 | | -- **Notion Databases**: Update your Notion pages to add posts or photos |
215 | | -- **Collections**: Modify `src/content/config.ts` for collection schemas |
216 | | -- **Loader**: Customize `src/lib/notion-loader.ts` for content processing |
217 | | - |
218 | | -### SEO & Feeds |
219 | | -- **Sitemap**: Configured via `@astrojs/sitemap` in `astro.config.mjs` |
220 | | -- **RSS Feed**: Customize `src/pages/rss.xml.ts` |
221 | | -- **JSON Feed**: Modify `src/pages/feed.json.ts` |
222 | | - |
223 | | ---- |
224 | | - |
225 | 47 | ## Scripts |
226 | 48 |
|
227 | | -| Script | Description | |
228 | | -|--------|-------------| |
| 49 | +| Command | Description | |
| 50 | +|---------|-------------| |
229 | 51 | | `npm run dev` | Start development server | |
230 | 52 | | `npm run build` | Build for production | |
231 | 53 | | `npm run preview` | Preview production build | |
232 | | -| `npm run check` | Run Astro type checking | |
233 | 54 | | `npm test` | Run tests | |
234 | | -| `npm run test:coverage` | Run tests with coverage | |
235 | 55 | | `npm run lint` | Run ESLint | |
236 | | -| `npm run format` | Format with Prettier | |
237 | | -| `npm run typecheck` | TypeScript type checking | |
238 | | - |
239 | | ---- |
240 | | - |
241 | | -## Troubleshooting |
242 | | - |
243 | | -### Common Issues |
244 | | - |
245 | | -**Content Not Loading** |
246 | | -- Ensure Notion integration has access to your databases |
247 | | -- Check that environment variables are set correctly |
248 | | -- Run `npm run build` to sync content from Notion |
249 | | - |
250 | | -**Build Failures** |
251 | | -- Ensure Node.js version is 24.x |
252 | | -- Clear Astro cache: `rm -rf .astro dist && npm run build` |
253 | | - |
254 | | -**Hydration Issues** |
255 | | -- Check for mismatches between server and client rendering |
256 | | -- Ensure React islands use `client:` directives correctly |
257 | | - |
258 | | -See [CLAUDE.md](CLAUDE.md#troubleshooting--known-issues) for more troubleshooting tips. |
259 | | - |
260 | | ---- |
| 56 | +| `npm run check` | Astro type checking | |
261 | 57 |
|
262 | 58 | ## License |
263 | 59 |
|
264 | | -This project is licensed under the [MIT License](LICENSE). |
265 | | - |
266 | | ---- |
267 | | - |
268 | | -## Acknowledgments |
269 | | - |
270 | | -- Built with [Astro](https://astro.build/) |
271 | | -- Powered by [Notion's Public API](https://developers.notion.com) |
272 | | -- Deployed on [Cloudflare Pages](https://pages.cloudflare.com/) |
273 | | -- Testing with [Vitest](https://vitest.dev/) and [React Testing Library](https://testing-library.com/) |
274 | | - |
275 | | ---- |
276 | | - |
277 | | -**Made with Astro and Notion** |
| 60 | +[MIT](LICENSE) |
0 commit comments