React Forum
Blog & Guide Responses
Jan 15, 2023 · 04:00 PM

[Guide] Build a Portfolio Site - implementation notes

Started by Ari Novak
guideportfoliotheme by side effectuseRenderEffectcreateScoperoute visible motionproject modalstored snapshotsField

The Portfolio guide is a good reminder that "simple sites" still benefit from a routing contract: file routes for sections, component-router motion for tabs/modals/theme/print posture, and stored derived project snapshots so every panel renders from a stable shape. The defining pattern is "theme by side effect": derive theme tokens in render and store them (scope or doc) so the tree reads from a ready-to-use palette.

Read the guide

Did you implement theme-by-side-effect (derive tokens in render), and how did you keep it from feeling like hidden magic? What did you store as derived "project snapshots" (preview lines, tag counts, visible ids) vs compute locally? How did you model the project modal overlay so it stayed route-visible and shareable? For contact forms, did you keep DOM truth and state truth alive together (Field posture), or commit on submit only?

Comments (24)

Back to latest
Imani Brooks
Jan 20, 2023 · 05:05 AM

Theme tokens derived in render was surprisingly nice.

Once tokens are stored, all the components can stay dumb and just read the palette.

Marisol Vega
Jan 28, 2023 · 07:30 PM

We rendered data-theme and data-print on the shell and it made "why does this look different" bugs easy.

A portfolio site has fewer bugs, but the ones you get are often posture bugs.

Dmitri Kline
Feb 11, 2023 · 09:40 AM

A minimal take on the guide's theme side effect, keeping it legible:

tsx
const ThemeScope = createScope({ theme: 'warm', tokens: { surface: '#fff', text: '#111' } });

function PortfolioShell({ children }: { children: React.ReactNode }) {
  const route = useGlobalRoute('portfolio', { theme: 'warm', print: 'off', section: 'home', projectId: null });
  const theme = useScope(ThemeScope);

  useRenderEffect(() => {
    theme.write('theme', route.read('theme'));
    theme.write('tokens', deriveThemeTokens(route.read('theme'), route.read('print')));
    return `theme:${route.read('theme')}:${route.read('print')}`;
  });

  return <section data-theme={theme.theme} data-print={route.read('print')}>{children}</section>;
}

Returning a string from the effect made it feel like documentation rather than a side channel.

Keira Santos
Feb 24, 2023 · 02:18 AM

Counterpoint: "theme by side effect" is easy to abuse.

If you start doing layout math or data writes in render effects, it can become hard to reason about.

Ari Novak
Feb 27, 2023 · 12:30 PM

Agree. We limited it to token derivation + evidence keys only.

If the effect can't be summarized as a one-line posture string, it's probably doing too much.

Grace McCoy
Mar 11, 2023 · 06:12 AM

Project snapshots were worth storing because they power multiple surfaces (grid, modal preview, sharing card).

We stored projectSnapshotById and tagCounts and it eliminated mismatches across panels.

Jasper Nguyen
Mar 29, 2023 · 06:55 PM

Modal overlay as route state is the best part of the guide.

We used projectId in global route state and rendered it as data-project so deep links were shareable.

Camille Duarte
Apr 17, 2023 · 04:22 AM

We also used local route state for the Work section tab (workTab=projects|speaking).

It made back/forward behave like a user expects without creating new file routes.

Owen Price
May 09, 2023 · 10:15 AM

For derived snapshots, we drew the line at "shared siblings": if both the Work grid and the modal need it, store it.

If only the modal needs it, compute it in the modal.

Nina Kapoor
May 31, 2023 · 09:40 PM

We kept a heroLines derived key because the home hero was used in both the on-page hero and the print layout.

Without storing it, the two versions drifted and it looked sloppy.

Benji Rios
Jul 12, 2023 · 08:03 AM

The print posture key (print=on|off) was a nice touch.

It forced us to decide which layout changes are posture vs which are just CSS.

Lina Ortega
Aug 22, 2023 · 06:18 PM

Contact form: we used Field posture (DOM truth + state truth) and committed on blur.

It made typing feel normal while still letting us render validation evidence as derived keys.

Amina Farouk
Oct 06, 2023 · 03:15 AM

Docs tie-in: this guide is a pretty direct example of "routing contracts" and "derived state" applied to a smaller app.

Related: Routing and Navigation and Performance and Rendering (Best Practices).

Theo Bennett
Nov 18, 2023 · 11:09 AM

A misunderstanding I had: I assumed a portfolio doesn't need route state because it's "static".

But as soon as you add modals and tabs, you have motion. Route state just makes it honest.

Hana Saito
Jan 09, 2024 · 11:59 PM

Counterpoint: storing too many derived strings (snapshots, previews, formatted dates) can make content updates annoying.

We had to re-derive everything when content changed, which was fine but it's work.

Imani Brooks
Jan 13, 2024 · 06:10 AM

We solved that by having one derivePortfolio() call that writes all derived keys from the source content object.

As long as there's a single derivation entry point, it's manageable.

Tomas Rivera
Mar 03, 2024 · 04:04 AM

We rendered a derived shareUrl for the current posture (section + projectId) and it made sharing easy.

It also prevented subtle drift where the URL didn't match the UI state.

Sofia Valdez
May 20, 2024 · 12:00 PM

Theme tokens were especially useful for a11y reviews.

Once tokens are stored, you can test contrast and surface rules consistently across the app.

Evan Brooks
Aug 14, 2024 · 07:55 PM

The guide made me stop using local state for modals.

Route-visible modal state is just easier to debug and share.

Salma Qureshi
Nov 02, 2024 · 07:30 AM

If you're worried about "magic", return a posture string from your render effect like the guide does.

It reads like documentation and gives you something to snapshot in tests.

Rowan Pierce
Mar 07, 2025 · 02:21 AM

We used derived tagCounts for filtering and it made the Work section feel more polished.

If the counts are derived live, they can flicker under refresh and it looks broken.

Mina Calder
Jul 19, 2025 · 03:12 PM

Print posture is also nice for debugging because it forces you to name layout modes explicitly.

If you ever have to explain layout differences, having a key helps.

Priya Liao
Nov 23, 2025 · 08:02 PM

We also stored a derived projectModalTitleLine so the modal header never had to recompute formatting.

It sounds minor, but it prevented drift between the grid card title and the modal title.

Inez Park
Feb 02, 2026 · 09:32 AM

A practical takeaway: route keys for motion, scope/doc keys for derived structures, and render evidence for posture.

Even for a portfolio, that makes the site feel intentional.

Jules Kwon
Apr 18, 2026 · 06:20 PM

This guide is a good entry point for the repo patterns because the domain is simple but the motion is real.

Avery Chen
Jul 09, 2026 · 04:44 AM

If you do theme tokens, keep the token set small at first.

Once the palette contract is stable, you can expand it without turning it into a huge refactor.