Problem
A personal engineering presence has to do two jobs at once: show the work and show how you think about systems. For v1 that meant a freeCodeCamp-compliant portfolio (fixed navbar, welcome hero, project tiles, profile link) that still read as deliberate architecture - not a theme dump or a framework demo I could not defend.
I did not yet need a blog, typed collections, or a multi-environment CDN. I did need:
- A constrained stack I could own end-to-end (vanilla HTML/CSS/JS)
- A build-time content seam so projects and profile data were not hand-copied into markup
- A design-system surface that documented tokens and patterns alongside the product
- Written decisions - ADRs and quality attributes - so future-me (and reviewers) could see why the stack looked the way it did
The risk of skipping that discipline was clear: a pretty single page that could not grow, could not explain itself, and would force a full rewrite the moment content or SEO became real requirements.
Solution
I built prj--personal-portfolio--v1 as a static site with an explicit architecture contract: vanilla frontend, Webpack 5 + Handlebars as a lightweight SSG, JSON content merged at build time, and GitHub Pages as the sole hosting path.
The product is two HTML outputs from one pipeline: the portfolio (index) - hero, about, technical arsenal, projects, experience, contact - and a design system page that documents colors, typography, components, and UI patterns (terminal, skill cloud, timeline, competency matrix). Multi-entry *.entry.js bundles keep each UI area's CSS/JS co-located with its Handlebars partial. Nine ADRs lock the framework, build, content, hosting, navigation, structure, Sass, and Babel choices.
Architecture
src/data/*.json <- profile, projects, pages, skills, …
src/markup/*.hbs <- index + design_system + partials/
src/styles/**/*.scss <- tokens, reset, themes, components
src/scripts/**/*.entry.js
│
▼
Webpack 5
HandlebarsPlugin -> dist/*.html (all JSON merged into context)
multi-entry JS/CSS -> named bundles
Sass -> MiniCssExtract -> minified CSS
Babel + Terser -> production JS
│
▼
GitHub Actions (push main)
npm ci -> npm run build -> peaceiris/gh-pages -> dist/
│
▼
GitHub Pages
paulalexserban.github.io/prj--personal-portfolio--v1/
| Layer | Responsibility |
|---|---|
src/data/ | JSON content: page shells, profile, projects, skills, competency matrix, experience (and stubs for coursework / arsenal) |
src/markup/ | Handlebars pages + partials; _template_base layout; section composition |
src/scripts/ | Multi-entry feature bundles (navbar, hero, terminal, about, contact, …) |
src/styles/ | Sass tokens / themes; CSS variables for light/dark |
build/ | Modular Webpack config: paths, loaders, plugins, entry glob |
| ADRs / ASRs | Decision log and quality attributes under docs/arch docs/ |
| CI | .github/workflows/deploy.yml -> build -> GitHub Pages |
Approach
As architect and lead implementer, I treated a "simple" portfolio as a system with boundaries - content, composition, assets, and delivery - and wrote the decisions down before the stack could calcify by accident.
Platform boundaries first
- Vanilla by ADR, not by default - ADR-001 rejects React/Vue/Bootstrap so the site demonstrates fundamentals and stays cheap to host; complexity cost is an accepted trade-off, not an oversight.
- Webpack + Handlebars as SSG - ADR-002/003: pages are templates; content is JSON merged into the Handlebars context at build; output is plain HTML/CSS/JS with no runtime server.
- Modular build config -
webpack.config.cjsstays thin;build/{paths,loaders,plugins,helpers}.cjsown the seams so the bundler remains evolvable. - Multi-entry "component bundles" - only
*.entry.jsbecome Webpack entries; partials pull their own CSS/JS. That is a design-system instinct without a framework. - Design system as a product page -
design_system.hbsships tokens, components, and patterns next to the portfolio so UI language is documented, not tribal.
Product surfaces
- Portfolio (
index) - fixed navbar with theme toggle and scroll spy; terminal-style hero; about + animated stats fromprofile.json; technical arsenal; project cards filtered tostatus === "Done"fromprojects.json; experience timeline; contact with social links and email obfuscation (encodehelper + decode on interaction). - Design system - colors, Inter + JetBrains Mono type, buttons/cards/forms, terminal / skill-cloud / timeline / competency-matrix patterns, and usage guidelines.
- Responsive + themed - Sass breakpoints and scale; runtime light/dark via
[data-theme]CSS custom properties andlocalStorage. - freeCodeCamp as ASRs - welcome section, navbar, projects, and profile link treated as architectural constraints, not afterthoughts.
Architecture knowledge management
- Nine ADRs covering framework, build tools, content, hosting, responsive strategy, navigation, folder structure, Sass, and Babel.
- ASRs and fitness-function notes - performance and maintainability written as requirements; Performance API probes in the base template as a lightweight runtime fitness signal.
- Docs as part of the deliverable - vision, goals, technical requirements, and improvement backlog live in-repo so the case for the next version is already evidence-based.
Stack
| Layer | Choices |
|---|---|
| Authoring | JSON in src/data/, Handlebars markup + partials |
| Build | Webpack 5, Handlebars plugin, Sass, Babel, Terser, CSS minimizer |
| Runtime | Vanilla ES6+ modules, CSS variables theme, no framework |
| Delivery | GitHub Actions -> GitHub Pages (dist/) |
| Governance | ADRs, ASRs, Dependabot, CODEOWNERS |
Design decisions that mattered
- Constraint as architecture. Choosing vanilla was a recorded decision with accepted risks (UI complexity ceiling, later refactor) - the same discipline later used when v2 chose Next.js and v3 chose SQLite-at-build.
- Content at build, not copy-paste HTML. Even with incomplete JSON->UI binding on some sections, the pattern (data files in, HTML out) is the direct ancestor of v2's
ContentRepositoryand v3's ingest pipeline. - Two surfaces, one pipeline. Portfolio + design system from the same Webpack graph proves the site is a small product platform, not a single landing file.
- ADRs on a personal project. Decision logs are leadership practice: future changes argue against written rationale instead of reinventing the stack.
- GitHub Pages only. Correct for v1 cost and FCC scope; named the hosting ceiling early (ADR-004) so v2's dual preview/prod path was a deliberate upgrade, not a surprise.
- Trade-off named up front. No blog, no typed multi-collection model, no SEO sitemap/IndexNow, no content/app repo split, no tests - accepted for v1, listed as the problem statement that justified v2.
What shipped
- Live static portfolio on GitHub Pages
- Two built pages: portfolio showcase and design-system documentation
- JSON-at-build content for page shells, profile, and projects (with data stubs ready for coursework / arsenal / experience expansion)
- Multi-entry Webpack asset graph with Sass themes and light/dark toggle
- CI deploy on
main, Dependabot, and a nine-ADR decision log - The foundational static-SSG practice that Personal Portfolio V2 and the later content pipeline evolved
Outcome
V1 established an online presence and, more importantly, a habit of platform thinking on a small surface: content separated from markup, build as a contract, design system as a first-class page, decisions written as ADRs. Its limits - co-located content, incomplete data binding, single hosting path, no typed collections or SEO release seam - are exactly what v2 was designed to fix. The line from this Webpack/Handlebars site to a multi-surface content platform is continuous, not a rewrite from zero.
Predecessor to the platform
This is the first generation of the personal engineering site. Related successors:
- Personal Portfolio V2 - Next.js SSG, private MDX/JSON content repo, tags, SEO (sitemap + IndexNow), GitHub Pages preview + AWS prod
- Content Pipeline SSG Platform - SQLite artifact feeding four surfaces in v3
- AWS multi-subdomain hosting - production edge for the current platform
- Local Traefik HTTPS stack - local domain parity for v3