Source for alfontal.dev, a personal site built with Astro and a notebook publishing workflow powered by Quarto.
The site mixes a few kinds of content in one static build:
- hand-authored Astro pages for the core site
- data-driven sections for projects, publications, CV, and homepage content
- notebook-based blog posts rendered from
notebooks/ - Quarto-generated standalone post pages bundled into the final site
.
├── notebooks/ # Source notebooks for Quarto-backed posts
├── web/ # Astro application
│ ├── src/
│ │ ├── pages/ # Routes
│ │ ├── data/ # Structured site content
│ │ ├── content/ # Generated notebook markdown entries
│ │ └── generated/ # Generated notebook manifest
│ ├── public/ # Static media plus generated Quarto post pages
│ └── scripts/ # Build helpers, especially notebook rendering
└── .github/workflows/ # Build and deploy automation
The Astro app lives in web/.
Most of the site is built from normal Astro pages and shared data files:
web/src/pages/index.astroweb/src/pages/projects.astroweb/src/pages/publications.astroweb/src/pages/cv.astroweb/src/data/content.ts
Notebook posts take a different path:
- Source notebooks live under
notebooks/<section>/<name>.ipynb. Shared notebook-level Quarto defaults live innotebooks/_quarto.yml, including the default HTML ToC settings, and individual notebook front matter can still override them. web/scripts/render-notebooks.mjsreads the first markdown cell and extracts YAML front matter.- Quarto renders each notebook twice:
- once to
gfmfor a generated Astro content entry
- once to
- once to
htmlfor a published Quarto-authored post page
- Companion assets are copied into
web/public/assets/notebooks/.... - The published Quarto page is written to
web/public/posts/<section>/<name>/index.htmland served directly at/posts/<section>/<name>/.
That setup keeps Astro focused on the main site while letting Quarto own notebook layout, ToC behavior, and wide-content rendering directly.
To build the site locally you need:
- Node.js 20+
- npm
- Quarto
Quarto is required because notebook posts are rendered during both local development and production builds.
Install dependencies:
cd web
npm installStart the site:
npm run devThat command automatically regenerates notebook-derived content first through the predev hook.
If you are actively editing notebooks and want the notebook pipeline to rerun automatically, use:
cd web
npm run dev:notebooksThis starts Astro and a watcher that reruns the notebook renderer whenever files under notebooks/ change.
Create a production build:
cd web
npm run buildThe output is written to:
web/dist
Preview the production build locally:
cd web
npm run previewThe core pages are edited directly in Astro or in the shared content file:
- page structure and layout in
web/src/pages/ - reusable site copy/data in
web/src/data/content.ts
Each notebook post should live in its own folder under notebooks/, for example:
notebooks/world-population/world_pop_densities.ipynb
The first markdown cell must contain YAML front matter. The renderer expects:
titledescriptiondatecategoriesauthorimage
Example:
---
title: Mapping world population density
description: Global population density maps built with xarray and plotnine.
date: 2025-11-04
categories:
- Data Visualization
- Python
author: Alejandro Fontal
image: featured_map.png
---Then regenerate the notebook content:
cd web
npm run notebooks:renderThe renderer will:
- validate the notebook front matter
- create generated Astro content in
web/src/content/notebooks-generated/ - extract Quarto HTML fragments into
web/src/generated/quarto-fragments/ - copy notebook-local assets into
web/public/assets/notebooks/ - update
web/src/generated/notebooks-manifest.json
Not every post has to come from a notebook.
For a fully hand-written page, add a normal Astro route under:
web/src/pages/blog/
Deployment is currently handled by GitHub Actions and GitHub Pages.
There is one workflow:
.github/workflows/web-build.yml- runs on pull requests, pushes to
main, and manualworkflow_dispatch - installs Node and Quarto
- builds the Astro site once
- on
main, uploads the GitHub Pages artifact - on
main, deploys that artifact through the native GitHub Pages Actions flow
- runs on pull requests, pushes to
The production site is therefore deployed directly from one GitHub Actions workflow to GitHub Pages. The source of truth stays in main; there is no separate published branch in the repository workflow anymore.
The repo also includes:
web/public/.nojekyllso GitHub Pages serves the static output as-is
If you want to deploy manually, the essential command is still:
cd web
npm run buildAnything that serves the generated web/dist directory can host the site.
For GitHub Pages specifically, the repository should be configured in:
- GitHub repository settings
PagesBuild and deploymentSource: GitHub ActionsCustom domain: alfontal.dev
The lightweight content check is:
cd web
npm run checkThis verifies that the main Astro pages still import content from the intended shared source.
The day-to-day publishing model is:
- Edit the Astro pages and shared content for the main site.
- Write notebook-backed posts in
notebooks/. - Run
npm run dev:notebookswhile working. - Build with
npm run build. - Push to
mainto trigger the deploy workflow.
That gives one static website with one deployment pipeline, even though some posts originate in Quarto notebooks.
