How this site works
Eight diagrams of this site's anatomy: what runs at build time, how layouts and slots compose pages, where the data and images come from, how search works with no server, and the little bit of JavaScript that runs in your browser right now.
<slot />: a hole content falls intoscript or head element1The big picture: it all happens at build time
Astro components are not shipped to visitors. They run once, on my machine or Netlify's, at build time. What comes out is plain HTML files. That is why the site is fast: your browser gets finished pages.
The source
src/
content/ (MDX posts, tags, projects)
pages/ (one file per route)
layouts/ + components/
The built site
dist/
index.html, blog/…/index.html
rss.xml, sitemap, llms.txt
optimized images + one CSS file
pagefind/ (the search index, see 7)
2Slots in sixty seconds
A <slot /> is a hole. Whatever you write between a component's opening and closing tags falls through into that hole. That is the entire trick that makes layouts work.
BaseLayout.astro declares the hole
<html>
<head>…</head>
<body>
<Header />
<main>
<slot /> ← the hole
</main>
<Footer />
</body>
</html>index.astro fills the hole
<BaseLayout title="Josh Pasholk"> <Hero /> <About /> <WorkSection /> <LatestPosts /> <RandomQuote /> ← all of this lands where the slot was </BaseLayout>
So every page gets the same head, header, and footer for free, and only has to bring the middle. Change BaseLayout.astro once and every page on the site changes.
3The homepage, as nested boxes
Boxes inside boxes literally is the architecture. Solid boxes are components; the dashed region is BaseLayout's slot, filled by whatever index.astro put between its tags.
BaseLayout
src/layouts/BaseLayout.astro (owns <html> and <head>)
theme script (is:inline)<Seo /> meta + JSON-LD<ClientRouter />Pagefind loader script
Header nav + search button and modal + ThemeToggle + mobile menu
Hero components/home/Hero.astro
About components/home/About.astro
WorkSection reads the projects collection
LatestPosts
components/home/LatestPosts.astro (newest 5 posts)
PostCard × 5 each renders TagPill + FormattedDate
RandomQuote components/home/RandomQuote.astro
Footer SocialIcon row (mastodon, github, routinehub, …)
4A blog post: layouts nest, so slots nest
A post page uses two layouts. PostLayout sits inside BaseLayout's slot, and declares a slot of its own, which is where the actual writing (the rendered MDX) lands.
BaseLayout
same shell as the homepage: head, Header, Footer
PostLayout
src/layouts/PostLayout.astro
post header: FormattedDate, title (view-transition name), TagPills
Prose wrapper typography styles for the article body
the MDX itself can import components: 11 posts pull in Link.astro and SocialIcon
prev / next post links
GitHub buttons script (data-astro-rerun)
Who fills what: the route file src/pages/blog/[...slug].astro looks up the post, calls render(post) to turn MDX into <Content />, and writes <PostLayout post={post}><Content /></PostLayout>. One file, two slots filled.
5Where the data comes from: props go down
Components never go looking for data. Data is loaded once, then handed down the tree as props (function arguments for components). One-way traffic.
MDX files
src/content/blog/*.mdx
frontmatter + bodyTyped collection
src/content.config.ts
title, date, tags…Helper
src/lib/posts.ts
getPublishedPosts()Component
<PostCard post={post} />
renders title, date, tagsThe same pipeline feeds the blog list, the tag pages, the RSS feed, the landing page's LatestPosts, and the SEO schema endpoints. One source of truth, many consumers.
6Images: three jobs, one build
There are 36 original images in src/assets/, and posts reference them with plain markdown. Nobody resizes anything by hand; the build does all three image jobs.
Originals
src/assets/
full-size photos + screenshots
referenced by 22 posts and Hero.astro
Optimized copies
dist/_astro/
resized, compressed, hashed filenames
share images: one per post, for link previews
Job one, content images: a markdown  in a post (or the <Image /> component in Hero) comes out the other side compressed and cache-friendly, automatically.
Job two, share images: PostLayout renders each post's feature image to a link-preview version at build time: capped at 1200px wide, never upscaled, and kept as PNG or JPEG because some link crawlers still choke on modern formats.
Job three, the passthrough bucket: public/ is for files that must not be touched: favicons, robots.txt, the redirects file, and the default share image. They are copied into dist/ exactly as-is.
7Search: no server, and a second kind of component
The search button in the header (Cmd+K works too) and the /search/ page run on Pagefind, which indexes the finished HTML right after the build.
Built pages
dist/**/*.html
only pages marked data-pagefind-body: posts, home, /uses/, /projects/
Search index
dist/pagefind/
index chunks + the UI bundle, all static files
Your browser
no API, no server
downloads only the tiny chunks that match
A second kind of component: everything in sections 2 through 4 was Astro components, which run at build time and disappear. The search UI is the opposite: web components, custom HTML tags that a script brings to life in your browser. The header renders just this:
<pagefind-modal-trigger instance="modal" compact></pagefind-modal-trigger> <pagefind-modal instance="modal" reset-on-close></pagefind-modal>
The empty <pagefind-modal> builds its own input, results, and keyboard hints once the loader script (that chip in section 3's diagram) fetches the bundle. In dev there is no index yet, so a CSS rule hides the undefined tags and the search page shows a fallback note instead.
8The browser layer: what happens on a click
Almost everything above ran at build time. Here is the small amount of JavaScript that actually runs for you, the visitor, in order, when you click a link on this site.
First visit: theme script runs before paint
The
is:inlinescript in the head readslocalStorage(or the system setting) and applies thedarkclass before any pixel renders. No flash.You click a link
<ClientRouter />intercepts it, fetches the next page's HTML, and swaps the document instead of doing a full reload. This is what enables the title morph animation between the blog list and a post.astro:after-swapfiresThe new document arrived without the
darkclass, so the theme script's listener re-applies it mid-swap. Still no flash.astro:page-loadfiresThemeToggle and the mobile menu re-bind their click handlers to the fresh DOM, RandomQuote picks a new quote, and the GitHub buttons script re-runs on posts that need it. The Pagefind loader also runs here: it fetches the search components once per visit and prunes anything from their registry that the swap just removed, so the search button never points at a dead modal.
The house rule that keeps all this working: on a view-transitions site, any JavaScript that touches the page must run on astro:page-load, not just once at startup.
One last thing: this page is itself a box in the section 3 diagram. You are reading it inside the very slot described in section 2. 🤙