By Adam Moore

Portfolio Page Lesson

A step-by-step custom field note on designing and building a portfolio page with strong positioning, server-rendered content, and bespoke visual sections.

May 3, 2026

Starting point

Resume

Runtime

TanStack + Nitro

Publishing

Custom admin

The goal was not to build an online resume with better colors. The goal was to make the resume behave like a small product: navigable, editable, opinionated, and specific enough that the implementation itself says something about the engineer behind it.

Decision 01

Let the resume stay honest

The page still has to answer ordinary resume questions: what have I done, what can I build, what do I care about, and how do you reach me. The trick is to stop treating those answers like a document export. Each section should make one claim and then back it up with visible evidence.

What this creates

The homepage becomes a sequence of chapters instead of a single scroll of credentials.

Decision 02

Use TanStack Start because the site is small, not because it is simple

A portfolio can look static while still needing real application boundaries. TanStack Start gives the site file routes, loaders, server functions, and typed links without splitting the work into a separate API project. That keeps the architecture legible.

What this creates

Public content is server loaded into the initial HTML, while private editing stays behind a custom admin.

Decision 03

Use SQLite before reaching for heavier machinery

The site needs durable content, drafts, ordering, contact submissions, sessions, and migrations. It does not need a hosted database dashboard or a distributed system. SQLite gives the project a real data layer while keeping the operational shape small.

What this creates

Content lives outside the code checkout in production, so deploys can change the app without replacing the data.

Decision 04

Keep Markdown fast, but do not let it own every idea

Most writing should be easy to publish. Some pieces need layout, rhythm, code panels, diagrams, or interactive sections. The system supports both by letting a post choose Markdown or a registered custom component.

What this creates

The same article route can render a plain field note or a designed essay without forking navigation and metadata.

Decision 05

Make deployment boring on purpose

A personal site is still production software once people can visit it. The deploy path runs checks, builds the Nitro output, applies migrations, and restarts a Node process on a custom Linux server. The server is where the app runs, not where the app is designed.

What this creates

GitHub Actions owns verification and release steps; the host receives a known build and keeps persistent data in one place.

Reading model

A portfolio has to explain what kind of attention it wants.

Someone scanning a resume is usually looking for keywords. Someone reading a portfolio is looking for judgment. The page needs enough structure to let both people move quickly without flattening the work into bullet points.

The page should teach the reader how to read you.

A strong portfolio creates a small language: chapter labels, repeated card shapes, a consistent accent color, and a rhythm that turns experience into evidence.

Architecture

The app structure is part of the argument.

The folder layout keeps the public experience, private publishing tools, custom field notes, and server-only data access easy to find. That is intentional. The site should be small enough to understand and structured enough to grow without becoming a pile of exceptions.

Folder structure

src/
  components/
    custom-admin/
    home/
  field-notes/
    custom/
      PortfolioPageLesson.tsx
      index.tsx
      metadata.ts
  routes/
    __root.tsx
    index.tsx
    field-notes.tsx
    field-notes/
      $slug.tsx
    custom-admin/
  server/
    content.server.ts
    posts.functions.ts
    posts.server.ts
db/
  migrations/

Server-rendered route data

export const Route = createFileRoute('/')({
  loader: async () => {
    const [coreStack, fieldNotes, footerLinks] = await Promise.all([
      listCoreStackItemsFn(),
      listPublishedPostsFn(),
      listFooterLinksFn(),
    ])

    return { coreStack, fieldNotes, footerLinks }
  },
  component: Home,
})

Server function wrapper

export const listPublishedPostsFn = createServerFn({
  method: 'GET',
}).handler(async () => {
  return listPublishedPosts()
})

Post content mode migration

ALTER TABLE posts
  ADD COLUMN content_type TEXT NOT NULL DEFAULT 'markdown'
    CHECK (content_type IN ('markdown', 'custom'));

ALTER TABLE posts
  ADD COLUMN custom_component TEXT;

Custom post rendering

if (post.contentType === 'custom') {
  const CustomFieldNote = customFieldNoteComponents[post.customComponent]

  return <CustomFieldNote post={post} />
}

return <article dangerouslySetInnerHTML={{ __html: post.html }} />

The database stores the editorial choice. The code owns the experience.

The custom admin does not need a visual page builder. It only needs to know whether a post is Markdown or custom, and which component key to use when it is custom. That keeps authorship simple while still leaving space for one-off designed pieces like this one.

Frontend system

The front end should feel authored, not decorated.

The homepage uses motion, chapter labels, dense cards, and a tight accent palette to create a recognizable system. The work is not in adding more effects; it is in making the effects behave well on real screens and letting the content stay readable.

Use responsive type and spacing so the page feels composed instead of merely squeezed.

Reduce background work on small and touch-first screens where decorative motion can become expensive.

Keep shared section patterns stable so custom article moments have somewhere to stand.

Use code snippets only when they reveal a decision the prose cannot explain as clearly.

The deployment pipeline is part of the craft.

The production setup keeps the SQLite database in persistent server storage, outside the repository checkout. That choice matters: content can survive deploys, migrations can run deliberately, and the server process can restart without treating the repo as the source of truth for everything.

GitHub Actions outline

name: Deploy

on:
  push:
    branches: [main]

jobs:
  ship:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: pnpm
      - run: pnpm install --frozen-lockfile
      - run: pnpm lint
      - run: pnpm test
      - run: pnpm build

Resources

The best resume-based portfolio does not hide the resume. It gives the resume an interface.

Portfolio Page Lesson

Read Next

More field notes

June 3, 2026

Building Your First Agentic Coding Harness

An open-source TypeScript harness for Grok, plus a line-by-line tutorial — sessions, profiles, compaction, sub-agents, and a REPL you can extend.

Read analysis

May 21, 2026

Agent Harnesses for Coding: What Actually Matters

After building and iterating on several of these systems, one lesson stands out clearly. Once you have a reasonably capable model, the majority of reliability, safety, iteration speed, and user trust comes from the harness around it. The model supplies reasoning and tool-call intent. The harness supplies everything else that turns that intent into coherent, recoverable work on a real codebase.

Read analysis