Frontend Architecture for a Personal Platform
How this Angular platform stays thin: app boundaries, a Blog-local content contract, and Portfolio linking to Blog through public URLs only.
Frontend Architecture for a Personal Platform
Good personal-platform architecture is mostly about what you refuse to build early.
This site is two Angular applications in one Nx repo. The interesting design is not the folder tree. It is the dependency arrow, the content boundary, and the rule that Portfolio may link to Blog without ever importing it.
Preferred layering
Keep the dependency arrow pointing downward:
Apps
↓
Feature libraries (when shared)
↓
Data-access / UI libraries
↓
UtilitiesFor V1, most of that stack still lives inside each app. That is intentional. A library without a second consumer is not a platform capability — it is a speculative package.
Portfolio and Blog are both thin apps. A thin app still owns:
- Bootstrapping
- Routing
- Layout composition
- Application-specific wiring
It should not own a fake “enterprise” content platform, a shared UI kit with one consumer, or a backend that nothing authenticates against.
If removing an abstraction does not hurt a real consumer, the abstraction was early.
What each app actually looks like
Portfolio is a single prerendered homepage: identity panel, intro, biography, experience, skills, journey, then a short Engineering notes section that points at Blog.
Blog is a small content app:
apps/blog/
├── content/articles/ # Markdown source of truth
└── src/app/
├── core/ # article model, repository, SEO
├── features/ # list, detail, not-found
└── layout/ # header, footerInternal Blog routes are / and /:slug. After the production mount they are /blog and /blog/<slug>. Presentation components never read Markdown files.
The content boundary
Markdown with YAML frontmatter lives under apps/blog/content/articles/. The filename is the slug. A build-time step (blog:prepare-content) turns that into typed modules. Angular then prerenders published routes.
Markdown + frontmatter
↓
prepare-content
↓
generated modules
↓
ArticleRepository
↓
list / detail UIPages depend on an article model, not on filesystem paths:
export type ArticleStatus = 'draft' | 'published';
export interface ArticleSummary {
slug: string;
title: string;
description: string;
publishedAt: string;
status: ArticleStatus;
tags: readonly string[];
}
export interface Article extends ArticleSummary {
contentHtml: string;
}ArticleRepository is Blog-local. It is not libs/data-access. List and detail talk to that boundary. When an Admin/API exists later, the repository implementation can change without rewriting the feature UI.
Drafts stay in Git and stay out of production prerender, sitemap, and RSS. That is a frontmatter concern, not an Admin product.
How Portfolio talks to Blog
The public contract is URLs, not source code:
Portfolio
|
| /blog
| /blog/<slug>
↓
BlogNot:
Portfolio → apps/blog/src
Portfolio → shared Blog library
Portfolio → backend → BlogThe homepage Engineering notes section needs titles, descriptions, dates, and tags. For this first integration, Portfolio keeps a small local list of featured article metadata and builds /blog/<slug> from the slug.
That list is duplicated presentation data. It is not Blog’s source of truth. The Markdown files still are. Duplicating two summaries is cheaper — and more honest — than inventing an API or coupling the apps so Portfolio can import Blog’s generated manifest.
Later, a public content API can replace both Blog’s Markdown-backed repository and Portfolio’s featured list. That future architecture is not required to put three cards on a homepage.
What this stack deliberately skips
| Temptation | Why it waits |
|---|---|
| Shared UI / data-access libraries | Only one consumer today |
| Module Federation | Independent deploys already give /blog |
| Backend, CMS, database | Git-owned Markdown is enough for V1 |
| NgRx | No cross-route client state that needs a store |
| Runtime Markdown fetching | Breaks static generation and pushes parsing late |
Both apps use standalone components, local Signals where UI state is tiny, and static prerender for SEO. Full dynamic SSR remains available if authenticated or highly dynamic routes appear. It is not needed for a landing page and a static blog.
Principles that actually help
- Prefer incremental change over speculative frameworks.
- Document decisions as ADRs when they change how the platform evolves.
- Keep presentation free of content-source details.
- Link across apps with public URLs.
- Extract a library when a second consumer exists, not when the planned tree looks incomplete.
Link out when demos belong elsewhere — for example to a future playground — rather than embedding interactive widgets in posts today.
The platform should grow because a requirement creates an architectural problem. Not because the folder tree is lonely.