Personal website and blog built with Astro. It syncs content from Notion (posts/projects/experience), ships a Substack subscribe embed, supports dark mode with a toggle and system detection, and is deployed via GitHub Pages. Tailwind + typography handle the styling.
Live site: https://ntemposd.me
- π Notion CMS - Posts, projects, and experience sync from Notion databases via API
- πΌοΈ Image handling - Downloads and caches images locally, preserves Notion captions as Markdown alt text
- π Dark mode - System preference detection with manual toggle, persists to localStorage
- π± Responsive nav - Mobile hamburger menu, desktop horizontal nav
- π§ Substack embed - Newsletter subscription iframe on post pages
- π¨ Tailwind CSS - Responsive styling with typography plugin for prose content
- π SEO basics - Sitemap generation and Open Graph meta tags
- Node.js 20.19+ (recommended)
- npm or pnpm
- A Notion account with API integration
git clone https://github.com/ntemposd/myastro.git
cd myastro
npm installCreate a .env file in the root directory:
# Notion API
NOTION_SECRET=secret_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
NOTION_POSTS_DB_ID=your-posts-database-id
NOTION_PROJECTS_DB_ID=your-projects-database-id
NOTION_EXPERIENCE_DB_ID=your-experience-database-id
# Optional: customize date property name
NOTION_POSTS_DATE_PROP=Date
NOTION_POSTS_STRICT_DATE=true
# Site configuration (local .env)
SITE_BASE_URL=http://localhost:4321
# Optional: Google Analytics (only if you use GA)
PUBLIC_GA_ID=G-XXXXXXXXXXCreate three Notion databases with the following structures:
Title(title)Published(checkbox) orStatus(select: "Published")Date(date)Excerpt(text)Tags(multi-select)Slug(text, optional - auto-generated from title if empty)Image(files or URL, optional)
Name(title)Published(checkbox)Excerpt(text)Tags(multi-select)Link(URL)Image(files or URL)
Role(title)Company(text)Start(date)End(date, optional - leave empty for current)Description(text)Location(text, optional)
- Go to Notion Integrations
- Create a new integration and copy the "Internal Integration Token"
- Share your databases with the integration
- Copy each database ID from the URL (the part after the workspace name and before the
?)
npm run devOpens at http://localhost:4321
myastro/
βββ public/ # Static assets served directly
β βββ posts/ # Copied images for dev (avoid 404s)
β βββ robots.txt
βββ scripts/ # Notion sync scripts
β βββ sync-posts.ts
β βββ sync-projects.ts
β βββ sync-experience.ts
βββ src/
β βββ assets/ # Bundled images (hashed in prod)
β β βββ posts/
β β βββ projects/
β βββ components/
β β βββ Share.astro # Social sharing buttons
β β βββ SubstackSubscribe.astro # Newsletter embed
β βββ content/ # Content collections
β β βββ config.ts
β β βββ posts/ # Synced from Notion
β β βββ projects/
β β βββ experience/
β βββ layouts/
β β βββ Layout.astro # Base layout with SEO
β βββ pages/
β β βββ index.astro
β β βββ about.astro
β β βββ projects.astro
β β βββ writing/
β β βββ index.astro
β β βββ [slug].astro
β βββ styles/
β βββ global.css
βββ package.json
| Command | Action |
|---|---|
npm install |
Install dependencies |
npm run dev |
Start dev server + sync content |
npm run build |
Build production site + sync content |
npm run preview |
Preview production build locally |
npm run sync:content |
Sync all content from Notion |
npm run sync:posts |
Sync posts only |
npm run format |
Format code with Prettier |
Content is automatically synced from Notion during dev and build. To manually sync:
npm run sync:contentHow it works:
- Fetches published entries from Notion databases via API
- Downloads images to
src/assets/posts/andpublic/posts/ - Converts Notion blocks to Markdown using
notion-to-md - Generates frontmatter from database properties
- Writes
.mdfiles tosrc/content/posts/
Note: Image captions from Notion sync as Markdown alt text (). The public/posts/ copy prevents 404s during development before client-side URL rewriting kicks in.
Edit src/layouts/Layout.astro to change:
- Site title and description
- Open Graph images
- Google Analytics ID
- Global styles:
src/styles/global.css - Tailwind config:
tailwind.config.ts - Typography (prose): Configured via
@tailwindcss/typography
- Update your Substack username in
src/components/SubstackSubscribe.astro - The embed iframe will load from
https://[username].substack.com/embed
Automatically deploys to GitHub Pages on push to main via .github/workflows/deploy.yml.
- Go to repository Settings β Pages
- Source: GitHub Actions
- Configure repository secrets and variables (see below)
NOTION_SECRETNOTION_POSTS_DB_IDNOTION_PROJECTS_DB_IDNOTION_EXPERIENCE_DB_IDPUBLIC_GA_ID(optional)
SITE_BASE_URL(live URL, e.g.,https://<user>.github.io/<repo>or your custom domain)
Push to main triggers automatic build and deploy.
Feel free to open an issue or reach out via ntemposd.me