A personal profile site does not need to behave like a social network, a booking platform, or a multi-author newsroom. When one person writes the content, understands the code, and only needs public pages, a small static architecture can be the more responsible choice. It removes whole categories of cost and maintenance instead of adding systems simply because a modern website could have them.

Why not write every page directly in HTML?

For a handful of fixed pages, writing the content directly in an Angular template would be simpler. This site also has a growing set of public articles in two languages, however. They share one article component and one route pattern; the slug selects the matching index entry and detail document. JSON is useful here because it separates structured content from the markup that presents it without introducing a database or CMS.

One page component can publish many article URLs.
/notes/:slug → ArticlePage component
                 ↓
       article index + detail JSON
                 ↓
      title, metadata, blocks, and FAQ
                 ↓
       prerendered HTML for that slug
text

The content is dynamic in the sense that one component can resolve many slugs from structured data; it is not fetched from a database for every visitor. During the build, the same JSON source generates Angular data, known static routes, Markdown copies, llms.txt, and related SEO output. Creating a separate HTML component for every article would repeat layout and publishing logic, make English and Indonesian content harder to keep aligned, and require each output format to be maintained separately.

The architecture: content in the repository

In this approach, page copy, project entries, and articles live as JSON files beside the application code. A small content generator validates that data and creates the source Angular needs at build time. Angular then prerenders each public route into static HTML, CSS, JavaScript, and assets. Firebase Hosting serves the finished files from its CDN.

The publishing and request path for this site.
BUILD TIME
data/*.json → content generator → Angular prerender → static files

REQUEST TIME
visitor → Firebase Hosting CDN → finished HTML, CSS, JS, and media
no database query and no request-time backend rendering
text

The important distinction is that JSON is a build-time source file, not a public API to maintain. The generator reads it before deployment and Angular places the page content into prerendered output. When a visitor opens an article, Firebase Hosting can return the finished static files from its CDN without asking a database or application server to assemble the page.

This is SSG rather than request-time SSR. Rendering happens once during the build instead of running server code for every page request. If a future feature needs to fetch a public JSON file in the browser, Firebase Hosting can serve and cache that file as another static asset too, still without backend processing. The current pages do not need that extra request because their content is already present in the generated output.

A separate JSON host is another valid pattern

Public JSON does not have to live inside the frontend repository. One implementation I work on is Wicked Campers Australia. Its Angular frontend reads pages, blogs, locations, vehicles, and specials from a separate static content server. This lets the content files be deployed and versioned independently, while several frontends can consume the same published source.

The personal site you are reading uses the same basic idea: public content is stored as read-only JSON rather than database records. The difference is where and when that JSON is read. This site keeps it in the frontend repository and places the content into static HTML during the SSG build. Wicked Campers publishes its JSON through a separate content server so a route can consume it during prerendering, request-time SSR, or in the browser, depending on how that route is delivered.

The same remote JSON origin can support different rendering modes.
CONTENT PUBLISHING
maintainer → JSON repository → static JSON host and CDN

BUILD OR PRERENDER
Angular build → remote JSON → generated static HTML → visitor

REQUEST-TIME SSR
visitor → Angular server → remote JSON → rendered HTML

BROWSER RUNTIME
visitor's browser → remote JSON CDN → update the page
text

The storage format is static in all three paths, but the time of the read changes the operating cost and failure mode. A prerender build pays the remote request once while generating the route; visitors then receive static output. SSR may read the content origin while handling a request, so the rendering server and remote host are part of that request path unless another cache has a fresh response. A browser-side read avoids server rendering but adds a cross-origin request before that part of the page can update.

That production configuration combines a prerender route list with an Angular Express renderer for regular routes. It also enables Angular hydration, whose HttpClient transfer cache can reuse eligible GET responses from server rendering during the first browser render. That avoids an immediate duplicate fetch, but it is separate from the CDN cache on the content server and from any longer-lived application cache.

This is better described as a static content origin than a database or compute microservice. It keeps database cost and backend transformation out of public content delivery, but it introduces other decisions: cache lifetime and version invalidation, CORS for browser reads, schema compatibility, a fallback when the content host is unavailable, and whether a content update must wait for a frontend rebuild.

Why it is lighter on the budget

The public-content side of the site needs no database to provision, back up, monitor, patch, or pay for. There is no always-running application server, content-management subscription, or client-side API required merely to render a profile or article. That saves both infrastructure cost and per-request work: a page view reads cached static files instead of triggering backend code and a database query.

A small profile like this can remain inside the no-cost quota of Firebase Hosting. With the included web.app or firebaseapp.com address, the hosting and HTTPS bill can therefore be zero. If I connect a custom domain, its registration and renewal may be the only recurring expense. This is a quota-based outcome rather than a promise of unlimited free hosting: larger media, retained releases, or substantially higher traffic can eventually require cleanup or a paid plan.

That does not make static architecture free or universally better. It makes the operating model smaller. A build and deploy are required whenever content changes, which is perfectly reasonable when the editor is the same person who maintains the code.

Separate public content from user-owned data

The JSON in this repository contains only public, publishable content. Visitors cannot edit it. A trusted maintainer changes the source and publishes a new build, so this content surface needs no public write API, database connection, or browser-facing admin panel.

Customer profiles, bookings, preferences, and other user-owned records have a different lifecycle. If the site needs them, the browser should call an authenticated application service with its own database and authorisation rules. That service can be a separate microservice when the wider system justifies the boundary. It must ensure that a user can read or edit only the records they are allowed to manage.

Two data paths with different ownership and write rules.
PUBLIC CONTENT
Admin or maintainer → repository JSON → static build → CDN
Visitors → read-only pages

USER-OWNED DATA
Signed-in user → authenticated API → application service → database
Authorisation → user may edit only permitted records
text
A smaller attack surface starts by putting each kind of data behind the boundary that matches its ownership.

Static does not mean invulnerable. The GitHub account, Firebase project, domain DNS, build dependencies, and any third-party form or analytics service still need proper access control and updates. Secrets, tokens, internal documents, and user records do not belong in the public-content repository or its generated files; dynamic records belong to the authenticated service designed to protect them.

Why Angular SSG and Firebase Hosting fit

Angular can build with static output and prerender known routes ahead of time. That means an article URL does not need a Node server to assemble its first page response. Firebase Hosting is designed to serve static web content over SSL from a global CDN, so it is a natural destination for the generated build folder.

The Angular setting that makes this a static build.
{
  "build": {
    "options": {
      "outputMode": "static"
    }
  }
}
json

SEO starts with useful pages, then the technical basics

Prerendered pages are helpful because each public route can ship as real HTML rather than waiting for a browser to assemble all of the text. But SSG alone does not create search rankings. Each page still needs a specific title and description, sensible headings, useful copy, image alt text, canonical URLs once a domain exists, and a sitemap generated with that real domain.

For this site, the content generator can create route-aware files such as a sitemap and machine-readable article copies during build. Those outputs should only be published when the site URL is confirmed; guessing a future domain would create incorrect metadata.

When this model is a good fit

This model suits a personal profile, technical notes from one author, public documentation, a simple landing page, or a small catalogue with no transaction flow. It works best when the content changes occasionally, the editor understands Git and code changes, and publication can happen through a deliberate build step.

Where the static boundary should end

A dynamic feature does not require every public page to stop being static. The profile, articles, and documentation can remain prerendered while accounts, comments, private dashboards, live inventory, payments, or user-editable data use a CMS or authenticated backend. The poor fit is mutable or private data being forced into repository JSON and republished as part of a public build.

The goal is not to prove that static websites are superior. The goal is to choose the smallest system that honestly fits the job. For one developer publishing a personal site, that can be more than enough.