Skip to main content
Adrean Jan Quidor (Kofeejan)
All blogs

My Portfolio Became a Content System

SanityAstroContent ArchitectureTypeScriptCloudflare

Problem

My portfolio started with visitor-facing content inside the frontend repository. That arrangement was direct, but every copy or media edit had the same delivery path as a code change. An editor needed a repository commit even when the layout and behavior did not change. Content also appeared in several forms, such as TypeScript values, image registries, and files in frontend folders. Those forms gave the frontend ownership over material that editors needed to change.

The hard part was not adding Sanity. The hard part was deciding what Sanity must own. A content management system can become a remote storage box for every visible string. That model moves data, but it does not define a useful boundary. It can also let editorial changes control route behavior, accessibility text, or temporary interface state.

I needed three clear terms. Content is visitor-facing editorial and marketing material. UI text is wording that explains interface state, validation, errors, accessibility, and route behavior. A Design Asset is a presentation file that belongs to the frontend, such as a decorative illustration that a component wires into a page.

Decision

I moved Content into Sanity, but I did not move every visible word. Sanity owns identity, navigation labels, calls to action, contact copy, articles, projects, services, certifications, search metadata, and Content Media. Content Media means images and other media that belong to editable Content. Editors can change these items without a portfolio repository commit.

The frontend still owns UI text. This includes form errors, validation messages, accessibility labels, route mechanics, transient state, and the 404 page. These words are part of software behavior. A code review must cover them with the behavior that they explain.

The frontend also owns Design Assets. The Nanaly illustration and decorative texture remain in the repository because they express presentation, not editorial meaning. A change to those files is a design implementation change. Sanity does not manage them.

This split follows a practical ownership test. If an editor must change an item without changing frontend behavior, the item is Content. If a developer must review the item with interaction or presentation code, the frontend owns it.

Implementation

The repository records this vocabulary in CONTEXT.md. The rules also appear in ARCHITECTURE.md and docs/harness/framework-standards.md. This repetition is deliberate because tools and contributors enter the repository through different paths.

The implementation removed the old src/data/ modules and Content Media registries. Sanity image assets replaced project screenshots, certification badges, and site brand media that lived in the repository. Sanity stores each image reference, crop, hotspot, width, height, and optional alternative text. The site turns that record into delivery URLs during a build.

Design Assets take a different path. Pages can use optimizeDesignAssetImage() and the frontend-owned imageOptimize.hero recipe. Pages cannot use that path for Content Media. The Display Content factory prepares Content Media before a page sees it. This separation prevents a page from treating an editor-owned image like a bundled design file.

Internal links also show the ownership split. Sanity owns the visitor-facing label, but it stores a Route Key instead of a path. A Route Key is a stable identifier for an internal destination. Frontend code maps contactProject to /contact?intent=project, for example. Sanity owns the words that a visitor reads. The frontend owns the route structure.

The same rule applies to files. The URL for the hosted CV is Content because an editor can change its destination. The PDF bytes remain outside Sanity. The repository calls that field a Hosted File URL, which prevents it from being confused with Content Media.

Rejected Options

I rejected the option to keep arbitrary marketing copy in TypeScript. That option made the code repository the editing interface. It also joined content review, code review, and deployment into one task when only the words changed.

I also rejected the option to move every visible string into Sanity. Form errors and accessibility labels need the same ownership as the interface logic that uses them. An editor must not change a validation promise while the validator keeps different behavior. Route paths have the same constraint because the frontend defines which pages exist.

I rejected a broad asset category as well. A single category called static assets does not say who owns a file. Content Media and Design Assets need different storage, review, and image preparation paths. A precise name makes the boundary visible in code.

Accepted Tradeoffs

This model creates more concepts than a folder of constants. Contributors must learn Content, UI text, Content Media, Design Asset, Route Key, and Hosted File URL. The code also needs adapters between the Sanity shape and the shape that pages render.

The benefit is not fewer files. The benefit is independent change. Editors can revise portfolio material through Sanity. Developers can change interface behavior without treating all copy as remote data. The site can reject invalid Content at a boundary before pages render it.

Some judgment remains. A call-to-action label is Content, while a form error is UI text. An illustration can support an article or define a frontend composition. The vocabulary does not remove judgment, but it gives each discussion a stable starting point.

Lesson

CMS adoption is an ownership design task before it is a data migration. A team gets little value from remote fields when each field still depends on frontend knowledge. The useful question is not, "Can this string live in the CMS?" The useful question is, "Who must change this item, and which behavior must change with it?"

The repository boundary became clearer after the content move because each category gained an owner. Sanity owns editable meaning. The frontend owns interaction, routes, and presentation. Build code translates between those owners.

Next-Project Rule

For my next content-backed frontend, I will classify copy, media, routes, and design files before I design schemas. I will put editor-owned meaning in the content system. I will keep behavior text and presentation assets with the code that gives them meaning. I will add an explicit adapter between the two sides instead of letting pages know how the content system stores data.