How SalienceNotes.dev Is Built

A technical deep dive into the architecture, content model, styling, and deployment pipeline of this site.

Contents

This post provides a complete, end-to-end technical overview of the architecture, folder structure, content model, pages, styling, integrations, build processes, and security posture of SalienceNotes.dev.

1. Project Overview

SalienceNotes.dev is a personal technical blog focused on cybersecurity, cloud platforms, AI security engineering, risk, and practical field notes. It is designed to share high-yield technical insights, lessons learned, and actionable takeaways from my work as a security engineer and architect.

The site is built as a static site using the Astro (v6) framework. Astro was chosen because of its “zero-JavaScript-by-default” footprint, which optimizes loading speed and delivery, while still permitting dynamic client-side enhancements where needed.

2. Folder and File Structure

The project is structured under an astro/ subdirectory. Here are the key folders and files:

  • src/: The core source code directory.
    • assets/: Local images and media.
    • components/: Reusable Astro components (e.g., headers, footers, theme toggles, share buttons, sidebars).
    • content/: Content collections (folders blog/ and projects/).
    • layouts/: Shared page templates. The main template is BlogPost.astro.
    • lib/: Local helper utilities (e.g., the reading-time.ts utility).
    • pages/: File-based routing definitions (Home, Writing page, About page, 404 page, and RSS feed).
    • styles/: Custom styling, housing global.css.
  • tests/: Contains Playwright end-to-end (E2E) tests.
  • astro.config.mjs: Framework integration configuration.
  • tsconfig.json: TypeScript rules configuration.
  • package.json: Lists dependencies, devDependencies, and node engine requirements.

3. Content Model

The content model leverages Astro’s native Content Collections.

Collection Schema

The blog collection is configured in src/content.config.ts using the Zod validation library. The schema validates the following frontmatter properties:

  • title: String.
  • description: String.
  • pubDate: Date (coerced format).
  • updatedDate: Date (optional).
  • draft: Boolean (defaults to false).
  • featured: Boolean (defaults to false).
  • tags: Array of strings (defaults to []).
  • heroImage: Image path (optional, validated using Astro’s image() helper).

Publishing & Flow

  • Drafts: Blog posts with draft: true are filtered out during static page generation and RSS generation.
  • Homepage Integration: The homepage (src/pages/index.astro) automatically queries the blog collection, filters out drafts, and displays a link to the single most recent article in the “Latest Field Note” banner.
  • Writing Page: The Writing page (src/pages/blog/index.astro) queries, sorts, and filters the collection. It extracts tags, count maps the primary topics, and populates the categories list on the sidebar. It uses both server-side logic (for default rendering based on URL parameters) and client-side JavaScript enhancements (for zero-latency list updates when switching categories or sorting orders).
  • Reading Time: Calculated dynamically at build time using the reading-time library via src/lib/reading-time.ts.

4. Pages and Routing

Astro’s file-system routing defines the site’s path structure:

  • Home (/): Handled by src/pages/index.astro. Displays the site’s definition of “Salience” and a call-to-action redirecting readers to Writing or About.
  • Writing (/blog): Handled by src/pages/blog/index.astro. Houses the blog post index, sorting controls, and category filter sidebar.
  • About (/about): Handled by src/pages/about.astro. Displays my professional profile, core role, and technical specification checklist.
  • Blog Posts (/blog/[...slug]): Dynamic route defined by src/pages/blog/[...slug].astro. Uses getStaticPaths() to pre-render all posts based on their IDs from the content collection.
  • 404 (/404): Custom error page (src/pages/404.astro).
  • RSS Feed (/rss.xml): Generated dynamically by src/pages/rss.xml.js.

Navigation uses the Header.astro and HeaderLink.astro components, which dynamically highlight active links based on the current URL.

5. Components

The site is built with highly modular, reusable Astro components:

  • Header.astro / Footer.astro: Provides site-wide structural layout, brand mark (SN), and navigation links.
  • ThemeToggle.astro: Handles theme switches using an inline script that sets data-theme on the html root and persists user choices in localStorage.
  • blog/BlogPostHeader.astro: Renders article metadata (dates, reading times, title, and description) on post pages.
  • blog/BlogPostSidebar.astro: Renders categories, tags, and a Table of Contents (TOC). In mobile view, this transitions into a <details> disclosures layout.
  • blog/BlogTocList.astro: Renders the dynamic lists of headings for the TOC.
  • blog/BlogShare.astro: Renders share options for X (Twitter), LinkedIn, and a native clipboard link copying button.
  • Ethos.astro: Included in layout templates to display the site’s publishing ethos.

These components compile down to static HTML, with small inline scripts providing interactivity where needed.

6. Styling and Design System

The site’s visual theme adopts a minimal, publication-style layout inspired by printed editorial media.

  • Vanilla CSS: Styled exclusively with a single global stylesheet (src/styles/global.css) without relying on Utility-first frameworks like Tailwind CSS.
  • Color Palettes: Features curated light and dark color schemes. The light mode uses a soft, paper-like background (#f7f5ef), dark slate borders, and dark gray text (#111827). The dark mode maps to deep navy slate colors (#0f141b / #151c25) and crisp off-white text (#eef2f7).
  • Fluid Typography: Responsive typography scales are declared at the :root level using CSS clamp() functions (ranging from --step--1 at 0.875rem up to --step-5 at 4rem), which automatically scale with viewport width without requiring complex media query overrides.
  • Spacing: A strict, standardized spacing scale (from --space-1 to --space-8) keeps vertical rhythm consistent across margins, paddings, and grid layouts.
  • Visual details: Uses micro-animations for theme toggles, underline offsets, and subtle borders. High contrast is maintained site-wide to align with accessibility standards.

7. Integrations and Features

The site remains lean by limiting integrations to core needs:

  • MDX support (@astrojs/mdx): Allows components and HTML structures to be embedded inside blog posts (e.g. <Callout>).
  • Sitemap Generator (@astrojs/sitemap): Automatically creates /sitemap-index.xml upon compilation to aid SEO indexing.
  • RSS feed (@astrojs/rss): Publishes updates at /rss.xml for readers using RSS feed readers.
  • Open Graph (OG) and Twitter Cards: Injected via BaseHead.astro using standard <meta> properties to ensure link previews render correctly on social channels.
  • Table of Contents (TOC): An inline script in BlogPost.astro utilizes an IntersectionObserver to track the user’s scroll position and highlight the active heading in the sidebar TOC in real-time.

8. Build and Development Setup

Development is governed by npm scripts defined in package.json:

  • npm run dev: Starts a local development server on port 4321.
  • npm run build: Compiles the site into static HTML/CSS assets in the dist/ directory.
  • npm run check: Runs Astro and TypeScript type-checking (astro check) to catch syntax or type errors before building.
  • npm run preview: Starts a local web server serving the built dist/ directory to simulate the production build.
  • npm run test:e2e: Runs end-to-end tests using the Playwright framework.

Testing Setup

The codebase includes Playwright testing (tests/e2e/) verifying:

  • Site-wide navigation flows.
  • Responsive design styling across viewports.
  • Correct loading and rendering of articles, sorting mechanisms (Oldest vs. Newest), category filtering sidebars, and the functional link-copying clipboard helper.
  • Absence of 404 errors for discovered internal links.

9. Deployment Readiness

The output format is static assets (dist/), making it highly portable.

  • Hosting Assumptions: Easily deployed on platforms like Cloudflare Pages, GitHub Pages, or Netlify. Since it’s pure static files, standard edge delivery can be leveraged without needing a Node.js runtime.
  • Environment Variables: The site is designed to run without runtime environment variables. The E2E tests reference process.env.BASE_URL to override the base URL if testing an active deployment.
  • Risks/Gaps:
    • Dynamic client-side features (such as sorting and category filtering) require JavaScript. While the backend templates do support server-side rendering parameters, on a static deployment, switching pages is required if JS is disabled.

10. Security and Maintainability

From an operational perspective, the static nature of the blog provides a robust security posture:

  • No Origin Servers: No runtime databases, server-side code execution, or administrative portals exist to be exploited, removing the main attack vectors common to platforms like WordPress.
  • Local Testing: Playwright E2E tests act as a regression guard, catching broken links, broken layouts, or scripting errors before deployment.
  • Strict Node Engine: The codebase locks Node.js version requirements (>=22.12.0) to avoid version mismatches across deployment environments.

Potential Improvements

To further mature the setup, I would recommend:

  1. Security Headers: Configuring a strong Content Security Policy (CSP), X-Frame-Options, and Permissions-Policy via the hosting provider’s edge rules (e.g., a _headers file for Cloudflare Pages).
  2. Automated Link Checking: Integrating a post-build command-line link checker to flag broken external links automatically.
  3. Dependency Automation: Introducing dependabot to keep package dependencies (especially framework dependencies) updated.

Lessons Learned

  1. Pragmatic Tooling: Astro v6 fits the requirements of static blogging perfectly. It separates content from layout, compile-checks markup, and preserves speed.
  2. Simple JS goes a long way: Using native browser features like IntersectionObserver for the table of contents and native web APIs (like the clipboard API for sharing) is fast and keeps bundle sizes minimal.
  3. End-to-End Tests for Blogs: Having automated E2E tests for a personal blog might seem like overkill, but it guarantees that adding posts or tweaking styles will not break layout, dark mode, or links.

What I Would Improve Next

  • Dynamic Search: Adding a client-side search indexing tool (like Pagefind) to allow real-time full-text search across all articles.
  • Automated Image Optimization: Enforcing WebP/AVIF conversions via Astro’s image optimization pipeline for all post assets.

Confidentiality Note

This review is conducted entirely on the public structure of this blog repository. It contains no client data, proprietary credentials, or private credentials.