Tools & Workflow

How to Code a Portfolio Website

A practical build path for designers: choose the simplest stack that fits the work, create the essential pages, and test the real portfolio before adding complexity.

Nikki Kipple
Nikki Kipple
14 min readUpdated Sep 2026

The short read

  1. Choose by maintenance

    Use the lightest stack that supports the content, interaction, and update rhythm you actually need.

  2. Build with real work

    Your longest title, largest case study, and least tidy image should shape the system—not demo content.

  3. Ship a useful path

    Identity, selected work, project context, navigation, and contact matter before polish or animation.

Paper portfolio wireframes, code studies, and responsive page proofs arranged on a worktable

Before you code, decide what the portfolio has to prove

A coded portfolio is not automatically more convincing than a site built with a template. Code gives you control over structure, behavior, performance, and maintenance. It does not decide which projects matter, explain your role, or turn screenshots into a case.

Start with the reader’s path. Someone should be able to identify who you are, choose relevant work, understand the context of a project, and find a real way to contact you. That path is the product. The framework is one implementation choice.

Write this down before opening the terminal

  • The role or kind of work the portfolio is making a case for.
  • The projects that provide the strongest evidence for that direction.
  • The shared information every project needs—and the details that should remain flexible.
  • The interactions that genuinely need code instead of a static presentation.
  • Who will update the site, how often, and with which level of technical comfort.

Choose the lightest route that fits the work

There is no universal portfolio stack. The useful question is what the site needs to render, repeat, publish, and maintain.

Choose by requirement—not by status

Static HTML and CSS
Use when the site is small, the content changes infrequently, and learning the browser fundamentals is part of the goal.
Content-focused framework
Use when project stories, writing, or image-heavy pages benefit from templates and content files without much client-side behavior.
Next.js or another application framework
Use when shared layouts, generated project routes, reusable components, data, or richer interaction justify the additional system.
Builder or hosted portfolio
Use when publishing speed, visual editing, and low-maintenance ownership matter more than demonstrating implementation craft.

A reason to add a framework

The content repeats

Projects share a reliable shape, navigation is reused, and one data change should update more than one surface.

A reason to stay simple

The site is already understandable

A few pages, modest interaction, and infrequent updates may not need a framework, data layer, or build-time abstraction.

Build the minimum useful portfolio first

Build one complete reader path before a design system, animation library, theme switcher, or CMS. The exact page count can vary, but the responsibilities are stable.

The first working version

  1. 1
    Shared orientation
    Name, role or focus, primary navigation, and a contact route that remain available across the site.
  2. 2
    Work index
    A homepage or selected-work page that helps someone distinguish and choose projects.
  3. 3
    One complete project route
    A project page that explains the question, your contribution, consequential decisions, evidence, and outcome or current state.
  4. 4
    Supporting context
    An About page, resume, or short background only when it helps the reader interpret the work.
  5. 5
    Production proof
    Direct links work, metadata is accurate, the domain resolves, and the site remains usable outside your own laptop.

The homepage and project page have different jobs. If you are still deciding how the work index should behave, compare the structures in the portfolio layout guide. Keep the case-study interior focused enough that it can also stand alone as a direct link.

Static HTML and CSS is a real starting point

HTML already provides meaningful elements for headers, navigation, main content, sections, articles, headings, links, and images. Starting with those elements keeps the document understandable before styling or JavaScript arrives.

A semantic portfolio shell
<header>
  <a href="/">Your Name</a>
  <nav aria-label="Primary">
    <a href="/#work">Work</a>
    <a href="/about.html">About</a>
    <a href="mailto:you@example.com">Contact</a>
  </nav>
</header>

<main>
  <section aria-labelledby="intro-heading">
    <h1 id="intro-heading">Product designer focused on…</h1>
    <p>A short, specific introduction.</p>
  </section>

  <section id="work" aria-labelledby="work-heading">
    <h2 id="work-heading">Selected work</h2>
    <!-- Project links belong here -->
  </section>
</main>

Use CSS Grid or Flexbox where the content needs layout, and let the document remain fluid. A grid based on available space is often more durable than a list of device-specific breakpoints.

A project grid that responds to available space
.project-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
  gap: clamp(1rem, 3vw, 2rem);
}

img {
  display: block;
  max-width: 100%;
  height: auto;
}

This path is especially useful when you want to understand the medium rather than hide it behind abstractions. You can still add a build tool, component framework, or content system later when a repeated problem appears.

Use Next.js when the system earns it

Next.js is a credible option for a portfolio with reusable layouts, generated project routes, structured content, or interactive work. It is not a requirement. The current Next.js installer’s recommended defaults include TypeScript, Tailwind CSS, ESLint, App Router, and Turbopack, so confirm the generated setup instead of following an older tutorial line by line.

Current quick start
npx create-next-app@latest my-portfolio --yes
cd my-portfolio
npm run dev

If you add Tailwind manually, follow the current framework guide. Tailwind 4 uses the PostCSS plugin and a CSS import; many older tutorials still describe the Tailwind 3 configuration flow. Keep the dependency surface small until the portfolio actually needs it.

Model the content without flattening every project

Reusable components are helpful when they preserve consistency in navigation, spacing, headings, credits, and project metadata. They become a problem when every case study is forced into the same number of sections regardless of the work.

A small shared project shape
export type Project = {
  slug: string
  title: string
  summary: string
  role: string
  cover: string
  tags: string[]
}

export const projects: Project[] = [
  {
    slug: 'project-name',
    title: 'Project name',
    summary: 'The problem and the change this work created.',
    role: 'Product design',
    cover: '/images/project-name/cover.webp',
    tags: ['Research', 'Interaction design'],
  },
]

Standardize

What helps readers orient

Title, summary, role, date, collaborators, project routes, media treatment, and basic section hierarchy.

Keep flexible

What the project needs to prove

Research evidence, process depth, prototypes, decisions, constraints, outcomes, and the order that makes the story credible.

Begin with local TypeScript, JSON, Markdown, or MDX when one person maintains the site. Add a CMS when publishing roles, volume, preview needs, or structured reuse make the extra system worthwhile.

Responsive behavior begins with reading order

A responsive portfolio is not the desktop composition compressed onto a phone. Preserve the meaning and priority of the page as space changes. Source order matters because it affects keyboard navigation, screen readers, and what remains understandable when layout styles simplify.

Verify the page—not just the breakpoint

  • Use semantic landmarks, headings, links, buttons, labels, and image alternatives for their intended jobs.
  • Keep the source order meaningful before using grid placement or absolute positioning.
  • Check narrow widths, wide screens, text enlarged to 200%, and 320 CSS-pixel reflow without avoidable two-dimensional scrolling.
  • Navigate every interactive control with a keyboard and keep focus visible.
  • Respect reduced-motion preferences and ensure essential information does not depend on animation.
  • Test the actual project titles, captions, credits, and images rather than tidy placeholders.

Accessibility checks should match the evidence. Measure contrast from the rendered colors, inspect the accessible name of controls, verify focus behavior, and test reflow in the browser. Do not claim a visual screenshot proves DOM structure, keyboard support, or alt text that has not been inspected.

Treat portfolio images as product content

Images often carry the largest visual and download cost in a portfolio. Export each image for its actual display job, provide dimensions, and avoid sending a desktop-sized asset to every narrow screen. Use responsive image markup or your framework’s image tools when they solve that delivery problem.

Give each image a job

Project cover
Help someone distinguish the project and decide whether to open it; do not hide the work inside a generic device mockup.
Evidence image
Support a specific claim in the story with enough detail, caption, and context to be understood.
Decorative image
Keep it out of the accessibility tree when it adds no information, and question whether its bytes are earning their place.
Largest visible image
Inspect whether it is discoverable early, correctly sized, compressed appropriately, and delayed by scripts or rendering work.

Measure the production site with browser tools and real-user data when available. A framework can provide useful defaults, but it cannot know whether your hero image, font loading, client-side code, or third-party scripts are the actual bottleneck.

Deploy early enough to find the real problems

A local build cannot prove the domain, direct routes, production environment, social previews, redirects, or deployment settings. Connect the repository to a host, use preview deployments for work in progress, and verify the production URL after the final merge.

A practical release check

  1. 1
    Build locally
    Resolve type, lint, link, and production-build failures before treating a preview as ready.
  2. 2
    Review the preview
    Open the homepage and a deep project link on desktop and mobile; check content, focus, media, navigation, and console errors.
  3. 3
    Verify metadata
    Confirm the title, description, canonical URL, social image, article dates, favicon, and robots behavior match the route.
  4. 4
    Test production
    After release, verify the custom domain, HTTPS, direct routes, forms or email links, analytics, sitemap, and representative performance.
  5. 5
    Make maintenance visible
    Record how to add a project, replace an image, update dependencies, and roll back a bad release.

Sources and current implementation references

Framework commands and hosting behavior change. The implementation details in this guide were checked against current first-party documentation on September 25, 2026.

QuestionsAnswers

Questions, answered.

A few practical details before you keep going

Share this resource

Nikki Kipple

Written by

Nikki Kipple

Product Designer & Design Instructor

Designer, educator, founder of The Crit. I've spent years teaching interaction design and reviewing hundreds of student portfolios. Good feedback shouldn't require being enrolled in my class — so I built a tool that gives it to everyone. Connect on LinkedIn →

The code can work while the portfolio still hides the story.

Share the live site as it stands. We will show what a reader can understand from the homepage now—and what a deeper Full Crit could examine across the portfolio shell.

Review my portfolio →