6 min read

Building a Data-Driven Case Study System in Next.js

How I separated project content from presentation in my portfolio so case studies can share one rendering system without turning each project into a custom page.

Next.jsReactTypeScriptArchitecturePortfolio

A portfolio can start as a collection of project cards and still become difficult to maintain surprisingly quickly. The moment each project needs an architecture diagram, engineering decisions, challenges, screenshots, links, and outcomes, putting all of that content directly into page components creates a maintenance problem.

I ran into that boundary while expanding my portfolio. Instead of creating a separate page implementation for every project, I moved the case-study content into a typed data model and kept the presentation in reusable Next.js components. The result is a system where adding a project means describing the project, not copying an entire page.

The content model is the contract

The case-study data is represented by a TypeScript interface with fields for the project identity, problem, solution, architecture, engineering decisions, challenges, future improvements, technology stack, status, role, impact, gallery assets, and external links. That interface is more than autocomplete: it defines what a complete case study is allowed to contain.

Keeping the model explicit also gives the page a stable contract. The rendering layer can assume, for example, that every case study has an overview and architecture section, while optional fields such as a gallery, package link, live link, or output example can be rendered only when present.

  • Project content lives in structured TypeScript data instead of JSX-heavy page files.
  • Required fields describe the common shape of a case study.
  • Optional fields handle project-specific capabilities without creating separate page implementations.
  • The same model can support both metadata generation and visible page content.

One page, many projects

The dynamic project route uses the case-study slug as its key and resolves the corresponding object from the data map. generateStaticParams then derives the known project paths from those same keys, so the route list does not need to be maintained separately.

The page itself is intentionally repetitive in a good way: it composes a hero, optional gallery, overview, problem, solution, architecture, engineering decisions, challenges, future improvements, and impact using reusable components. A new case study therefore changes the data rather than the page structure.

  • The route resolves a project by slug from one data source.
  • generateStaticParams derives the known case-study paths from the data keys.
  • Reusable components own the visual treatment of repeated sections.
  • Project-specific links and galleries are driven by optional data fields.

Optional data without optional chaos

Real projects do not all have identical outputs. Some have a live deployment, some have a package release, and some need an output example or a different gallery treatment. The model handles that variation explicitly instead of forcing every project into the same set of placeholder sections.

The page checks those optional values at the rendering boundary. For example, the live and package actions appear only when their URLs exist, a gallery is rendered only when images are provided, and the output-example section changes the numbering of the sections that follow it. The important detail is that the variability is data-driven rather than duplicated in multiple page files.

Metadata should use the same source

The same case-study object also drives the route metadata. The page generates its title and description from the project record and derives the canonical URL from the same slug used for the page itself. That prevents the visible page and its SEO metadata from becoming two independent representations of the project.

Next.js's Metadata API is designed for both static metadata and dynamic generateMetadata functions, while metadataBase provides a central base for relative metadata URLs. In this portfolio, the case-study route keeps the project-specific values local while the root layout establishes the site's production origin.

Galleries belong in the content model too

Project screenshots are content, not layout logic. The case-study model therefore stores a gallery array and an optional fit strategy. The gallery component only needs to know which images to show and whether they should be treated as cover-style or contain-style assets.

This becomes especially useful when projects have very different visual footprints. A mobile application can use contain-style screenshots while a web dashboard can use a wide cover treatment, without changing the case-study page itself.

  • Store gallery paths alongside the project they describe.
  • Keep image presentation rules as small data-level variations.
  • Let the gallery component own image rendering and layout.
  • Avoid embedding project-specific asset decisions directly in the route.

The homepage can reuse the same idea

The case-study system is not isolated from the homepage. The portfolio's Projects section also consumes structured project data and uses reusable presentation components for featured and additional projects. That creates two related content surfaces with the same general principle: data describes the project while components describe how projects are displayed.

The benefit is not just less code. It makes the portfolio easier to evolve. A visual redesign can change the presentation layer without rewriting every project's content, and a new project can be added without introducing another one-off page.

Why this matters as a portfolio grows

A portfolio is often treated as a static website, but the engineering behind it still benefits from application architecture. Once the site has multiple projects, case studies, images, metadata, and external links, content duplication becomes a source of bugs just like duplicated application logic.

The data-driven approach keeps the boundaries explicit: structured data describes what a project is, route components decide how that information is assembled into a page, and shared UI components decide how repeated sections look. That makes each layer easier to reason about and easier to change.

The broader lesson

The useful part of this architecture is not that it is a portfolio. The same pattern works anywhere a product has many entities with a shared presentation shape: documentation pages, product catalogs, knowledge bases, customer stories, and internal dashboards all benefit from a typed content model and reusable rendering layer.

When several pages have the same structure, I would rather make the structure explicit in the data and components than copy the page and hope the versions stay synchronized. The portfolio became easier to extend once the content stopped being coupled to the markup.