Skip to main content

Architecture

This site is a personal website built with Vue and Nuxt. Since most of my professional work lives in private enterprise systems, I use this project to show how I structure a frontend application, manage content, and handle performance and accessibility.

  • Nuxt 3 & Static Pre-rendering: Every page is pre-rendered to static HTML at build time, which keeps the site fast, simple to host, and easy for search engines to crawl.
  • Vue 3 Composition API: I use the Composition API and script setup to keep component logic easier to follow.
  • Pinia State Management: Dedicated stores manage static content distribution and global application theme states without prop drilling.
  • SCSS & CSS Custom Properties: The design system uses scoped SCSS modules and CSS custom properties to support theming and responsive layout.
  • SCSS Auto-injection via Vite: Variables and mixins are injected into each component’s style block through Vite config, which keeps shared styling tools available without repetitive imports.
  • Page Transitions: A simple global fade transition is configured in nuxt.config.ts, with a dedicated partial handling the route transition classes.

Performance & Lighthouse

This site is pre-rendered, keeps JavaScript light, and avoids extra dependencies where native browser behavior is sufficient. Recent Lighthouse checks have scored the site highly across performance, accessibility, best practices, and SEO.

100
Performance
100
Accessibility
100
Best Practices
100
SEO
  • Pre-rendered HTML: Nuxt generates fully-formed HTML at build time, so browsers render content immediately without waiting for client-side JavaScript.
  • Inline SVG Icons, No Icon Library: Every icon on the site (competencies, social links, resume download, theme toggle, and navigation) is inlined directly into the component. The page ships zero icon-font or icon-library requests.
  • Minimal JavaScript: No heavy UI libraries or animation frameworks. The bundle stays light by leaning on native browser APIs and Vite's default tree-shaking.

Content-Driven Architecture

All page content lives in a single data/content.js file as a plain JavaScript export. A Pinia store wraps it so components can access the content they need without passing data through multiple layers.

It is a simple content-driven setup that keeps structure and presentation separate. If I ever wanted to swap this for a CMS or an external API, that change would mostly happen in the store layer rather than across the component tree.

The pre-render config in nuxt.config.ts only includes routes that exist in the data layer, which keeps the build output predictable.

Anti-FOUC Theme Initialization

Theme persistence is handled by a Pinia store that reads localStorage and applies a .light-mode class to the root element. The issue is that Pinia initializes after first paint, so returning users could briefly see the wrong theme on load.

To avoid that flash, I inject a small inline script into the document head through app.head.script in nuxt.config.ts. It runs before the page renders, checks localStorage, and applies the saved theme immediately.

Hash-Based Deep Linking

The resume timeline supports URL hash navigation, so a link like /resume#best-egg opens the relevant employer section and scrolls directly to it.

The implementation watches route.hash, matches it against a slugified employer heading, and adds the relevant entries to an expandedRoles Set. A custom useScrollToHash composable waits for the DOM to finish updating before calculating scroll position, and it accounts for the sticky header so section headings are not hidden.

CI/CD & Edge Delivery

This repository is connected to an automated CI/CD pipeline, so every push triggers a build that generates pre-rendered static assets and deploys them through Netlify’s CDN.

Testing & Coverage

The site is covered by a Vitest unit suite running in the Nuxt test environment with happy-dom. Components, pages, stores, the layout, the router scroll behavior, the theme plugin, and the scroll-to-hash composable all have their own specs.

  • Vitest + @nuxt/test-utils: Tests boot a real Nuxt context via mountSuspended, so auto-imports, composables, and Pinia all resolve the same way they do at runtime.
  • Coverage Targets: The v8 coverage reporter tracks every source file under components/, pages/, stores/, composables/, plugins/, layouts/, and app/. The suite hits 99% lines, 99% statements, 99% functions, and 91% branches. Remaining gaps are defensive optional chains and the SSR guard.
  • Focus on Behavior: Tests assert rendered output, route-driven state (hash-based timeline expansion), and user interactions (theme toggle, mobile menu open/close, focus trap) rather than implementation details.
  • Fast Local Loop: The full suite runs in about a second and a half via npm run test, with npm run test:watch for iteration and npm run test:coverage to refresh the report.

Accessibility (A11Y)

Accessibility was part of the build from the start.

  • Skip Navigation: A skip-to-content link lets keyboard and screen reader users bypass the header and jump straight to the page content.
  • Semantic HTML: Proper heading hierarchy, landmark elements, and native interactive controls throughout.
  • Keyboard Navigation: Every interactive element, including the expanding resume timeline and mobile navigation, is fully operable via keyboard with visible focus indicators. The mobile menu traps focus while open and closes on Escape.
  • Inert on Collapsed Panels: The timeline uses the inert attribute on collapsed content panels rather than just aria-hidden. inert removes keyboard focus, pointer events, and accessibility tree presence in one attribute, which is what this pattern actually needs.
  • Reduced Motion: A global prefers-reduced-motion media query disables animations and transitions for users who request it.
  • ARIA and State Attributes: Custom components like the timeline toggle, theme switch, and mobile menu use appropriate roles, labels, and state attributes for assistive technologies.
  • Dual-Theme Contrast: Both the dark and light themes use color pairings chosen to maintain readable contrast ratios.