Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelogContact
  1. Home
  2. Blog
  3. Build it: screens and apps
  4. Nextjs Portfolio Template That Wins Clients
Auf Deutsch lesen
  1. Blog
  2. Build it: screens and apps
  3. Nextjs Portfolio Template That Wins Clients
Build it: screens and apps
Build it: screens and apps

Nextjs Portfolio Template That Wins Clients

SR
Serban Rusu · Founder of Wingo UI
Nov 6, 2026 · 9 min read

On this page

0%
  1. What do the popular Next.js portfolio templates leave out?
  2. Which sections does a portfolio that wins clients need?
  3. Why open case studies in a sheet instead of a new page?
  4. What should a developer cut compared to a designer?
  5. How do I set it up for a freelance developer?
  6. How do I wire the contact form?
  7. How does the portfolio read on a phone?
  8. Is there a free version, and what does Pro cost?
  9. FAQ
    1. What should a developer portfolio include?
    2. Is there a free Next.js portfolio template?
    3. Should a portfolio show prices?
    4. How should case studies work on a phone?
    5. Is this a shadcn portfolio template?

TL;DR

A portfolio that wins work answers three questions fast: what you did for clients like me, what it costs to hire you, and how to reach you. Wingo UI's Portfolio Page is a Next.js template with a hero, case studies that open in a sheet (a bottom sheet on phones), an experience timeline, priced services, writing and a validated contact form, all filled from props, and every section after the work can be switched off. Cut services for a job hunt, cut testimonials until you have real ones, and keep the contact form on every version.

Published Nov 6, 2026

Most people who search for a nextjs portfolio template want a site that brings in work: a client, a contract, an interview. What they usually find is a nice hero, a grid of every side project with GitHub stars, and a mailto link at the bottom. That is a resume dump with animations. This guide covers what a portfolio page needs to turn a visitor into a message, how Wingo UI's Portfolio Page template is built around that, what to cut if you are a developer instead of a designer, and how the whole thing reads on a phone.

What do the popular Next.js portfolio templates leave out?

They mostly cover the stack and skip the selling. The GitHub topic page for nextjs-portfolio-template (opens in a new tab) listed 25 public repositories when we checked on November 6, 2026. The most-starred entries describe themselves by stack (Next.js 16, Tailwind CSS, Framer Motion, Three.js) or as someone's personal site that others may reuse, not by the sections they ship. Vercel's portfolio template gallery (opens in a new tab) (checked the same day) lists Next.js starters such as the Portfolio Starter Kit, Magic Portfolio for Next.js and a portfolio with a pageview counter. Their one-line descriptions mention Markdown, a blog, projects and a gallery; none of them mention case studies, prices or a contact form.

Those starters do what they promise. If you want a free personal site with a Markdown blog by tonight, the Portfolio Starter Kit is the one we would pick. The gaps show up when the site has a job to do:

  • Case studies instead of thumbnails. A card with a title and three tags tells a client nothing. The problem, your role, the process and a result do.
  • A price anchor. "From $6,500" filters out bad fits and gives a serious client a reason to write.
  • A contact form with validation and an error state. A mailto link does nothing useful for a visitor without a mail app set up, and you never learn they tried.
  • A phone layout that keeps the visitor's place. A new route per case study means a full page change and a trip back to the grid between every two projects they compare.

Which sections does a portfolio that wins clients need?

Six, in this order: hero, selected work, experience, services, testimonials and writing, and contact. Portfolio Page is a Tailwind portfolio template that renders all six by default; data props fill them, and show* props turn off the optional ones:

SectionWhat it doesProp
HeroName, role, one-line pitch, availability pill, Start a project and See my work, key factsowner, labels.heroTitle, facts, available
WorkCase study cards in a grid or a list; each opens a sheetprojects, layout
ExperienceRoles on a vertical timeline, the current one marked Nowexperience, showExperience
ServicesOffers with starting prices; one can be featured with a Most booked badgeservices, showServices, currency
Testimonials and writingClient quotes and short essaystestimonials, posts, showTestimonials, showWriting
ContactEmail with a copy button and a form that checks name, email and messageonContact, showContact

The experience band is our Timeline Section with a dot marker and years that stick beside each card on desktop, and the quotes are the Testimonials Section. Both take overrides through experienceProps and testimonialsProps, so a horizontal timeline or a carousel of quotes is one prop away.

Scroll the page, then tap a project card: the case study opens in a sheet. Book this on a service jumps to the form with that service picked
$ npx wingo-ui@latest add portfolio-page
ProPortfolio Page docs

Why open case studies in a sheet instead of a new page?

Because the visitor is comparing your projects, and a sheet lets them do that without losing the grid. Each card is one button. It opens the Sheet component from the right at the xl size, with the client, year, role and timeline, the problem, numbered process steps, outcome numbers and a swipeable image strip. The arrow buttons in the footer step to the previous or next project, Escape closes it and focus returns to the card that opened it. Start a similar project closes the sheet and puts the cursor in the contact form.

The sheet is controlled through project and onProjectChange, or seeded with defaultProject, so you can mirror the open case study in the URL and share a link to it. Read the query in the server page and write changes back with the native History API, which Next.js keeps in sync with its router:

tsx
// app/portfolio.tsx
"use client";
import { PortfolioPage } from "@/components/templates/portfolio-page";
export function Portfolio({ initialProject }: { initialProject: string }) {
return (
<PortfolioPage
defaultProject={initialProject}
onProjectChange={(slug) => {
const url = slug ? `?work=${encodeURIComponent(slug)}` : window.location.pathname;
window.history.replaceState(null, "", url);
}}
/>
);
}
tsx
// app/page.tsx
import { Portfolio } from "./portfolio";
export default async function Page({ searchParams }: { searchParams: Promise<{ work?: string }> }) {
const { work } = await searchParams;
return <Portfolio initialProject={work ?? ""} />;
}

Reading searchParams renders the route on each request, and the whole portfolio stays in the server HTML. Calling useSearchParams in the client instead would, on a prerendered route, client-render everything up to the nearest Suspense boundary, which here is the entire page. If you want each case study indexed on its own, keep a real /work/<slug> page for search engines too; the sheet is for people already on the page.

What should a developer cut compared to a designer?

Fewer pictures, more proof that the thing runs. The sample data is a product designer, Emma Carter in New York. To turn it into a developer portfolio template, swap the content and keep the layout:

SectionFreelance developerProduct designerLooking for a job
Workcover: "flow" or "dashboard", an href to the live product, a short processPhotos in gallery, a longer processTwo or three projects, your role spelled out
ExperienceKeep, shortKeepKeep, this is the resume
ServicesKeep, with pricesKeep, with pricesshowServices={false}
TestimonialsOnly real quotes you have permission to useSameOff until you have them
WritingKeep if you writeKeep if you writeKeep, it shows how you think
ContactKeepKeepKeep

Two rules hold for everyone. Do not fill the testimonials band with placeholder quotes, because a visitor who spots one fake quote stops trusting the real ones; showTestimonials={false} until you have two or three. And an empty facts array hides the hero facts, which is better than "5+ years" written with nothing to back it.

How do I set it up for a freelance developer?

Install the template. It works like any shadcn portfolio template: the CLI copies the source into your project, and you own the files. It brings the sheet, the timeline, the testimonials and blog sections, the form fields and the shared hooks:

bash
npx wingo-ui@latest add portfolio-page

Then replace the sample content. The types live in the data file next to the template:

tsx
// app/page.tsx
"use client";
import Link from "next/link";
import { PortfolioPage } from "@/components/templates/portfolio-page";
import type {
PortfolioExperience,
PortfolioProject,
PortfolioService,
} from "@/components/templates/portfolio-page-data";
import { sendMessage } from "./actions";
// replace the client, copy and numbers with your own real work
const projects: PortfolioProject[] = [
{
slug: "globex-checkout",
title: "A checkout rebuilt as one page",
client: "Globex",
summary: "Moved a five-step checkout to one Next.js page with server actions and a shared form kit.",
year: "2026",
role: "Lead frontend engineer",
duration: "10 weeks",
tags: ["Next.js", "Performance", "Forms"],
color: "#2563EB",
cover: "flow",
problem: "Customers dropped off between the shipping and payment steps, and every step was a separate page load.",
process: [
{ title: "Measured the funnel", description: "Added events to each step to find where people left." },
{ title: "Merged the steps", description: "One page, one form, validation on the server and on blur." },
{ title: "Shipped behind a flag", description: "Ran both checkouts side by side for two weeks." },
],
outcome: "The one-page checkout replaced the old flow for every customer.",
metrics: [{ value: "1 page", label: "instead of five" }],
href: "https://example.com",
},
];
const experience: PortfolioExperience[] = [
{ company: "Independent", role: "Frontend engineer", start: "2023", description: "Next.js builds and audits for startups." },
{ company: "Globex", role: "Senior engineer", start: "2019", end: "2023", location: "Austin", description: "Led the web checkout team." },
];
const services: PortfolioService[] = [
{ id: "audit", title: "Performance audit", description: "A written report with the ten fixes that matter most.", price: 2500, duration: "1 week", features: ["Core Web Vitals review", "Bundle analysis", "A call to walk through it"] },
{ id: "build", title: "Feature build", description: "One feature from spec to production.", price: 9000, duration: "4 to 6 weeks", features: ["Weekly demos", "Tests and docs", "Two weeks of support"], featured: true },
{ id: "partner", title: "Frontend partner", description: "A few days a month on your codebase.", price: 6000, unit: "/ month", features: ["Code review", "Architecture calls", "Pairing with your team"] },
];
export default function Home() {
return (
<PortfolioPage
linkComponent={Link}
owner={{ name: "Liam Brooks", role: "Frontend engineer", location: "Austin", email: "liam@example.com", socials: [{ label: "GitHub", href: "https://github.com" }] }}
labels={{
heroTitle: "I build fast, accessible Next.js apps.",
heroDescription: "Frontend engineer in Austin. I help small teams ship checkouts, dashboards and design systems.",
experienceTitle: "Seven years of shipping UI",
}}
facts={[]}
projects={projects}
experience={experience}
services={services}
layout="list"
showTestimonials={false}
showWriting={false}
onContact={sendMessage}
/>
);
}

layout="list" gives each project a wide card with the cover beside the text, which suits a developer with fewer, deeper projects. Because this page is a client component, export const metadata cannot live in it: set the title and description in app/layout.tsx, or move the markup into its own client file as in the URL example above and keep page.tsx on the server. The services also become the choices in the form's "What do you need?" group, so a visitor who taps Book this on the audit lands in the form with the audit picked.

How do I wire the contact form?

Pass onContact a function that returns a promise. The button spins while it runs; a rejection keeps everything the visitor typed and shows the error above Send message. Without onContact the page waits 900 ms and thanks the sender without sending anything, which is fine for a demo and a lost lead in production. A server action works:

ts
// app/actions.ts
"use server";
import type { PortfolioContactValues } from "@/components/templates/portfolio-page";
export async function sendMessage(values: PortfolioContactValues) {
// the form already checks these on the client; check again on the server
if (!values.email.includes("@") || values.message.trim().length < 20) {
throw new Error("Invalid message");
}
// send { name, email, projectType, message } with your mail provider here
}

The client checks match: a name, a valid email and at least 20 characters about the project, with focus moved to the first problem. If you need a full contact page with office hours, channels and a map instead, the Contact Page template is free.

How does the portfolio read on a phone?

It is built phone first. Below 768px the work grid and the list layout both collapse to one column, and the case study sheet comes up from the bottom (mobileSide="bottom" on the sheet) with a handle you can swipe down. The timeline moves the years into the cards, and the image strip inside a case study scrolls sideways with a swipe. Once the hero scrolls away, a Hire me bar slides up from the bottom, padded above the iPhone home indicator with env(safe-area-inset-bottom). It hides again at the contact band and while a case study is open, so it never covers the form or the sheet. Turn it off with stickyBar={false}.

The page starts with the Northwind case study open. In this narrow frame it is a bottom sheet: swipe it down to close, or use the arrows to step through the projects
$ npx wingo-ui@latest add portfolio-page
ProPortfolio Page docs

The page also respects reduced motion: the hero entrance, the glide to the contact band and the sheet transitions turn instant, and the hero never animates during hydration, so the server HTML is the final layout.

Is there a free version, and what does Pro cost?

Portfolio Page, Timeline Section and Testimonials Section are Pro items; the Sheet and the Contact Page are two of the 75 free items in the 369-item catalog as of November 6, 2026. Pro is $8 a month, $80 a year or $150 once for lifetime access, see pricing. If a free nextjs portfolio template from GitHub or Vercel does what you need, use it. If you want the case studies, prices and form already built, with light and dark both designed, install the template and spend the evening on your content instead.

For more full pages built the same way, browse Build it: screens and apps or start from our SaaS website template guide, which covers the marketing site a product needs around a page like this one. If a team sells the work instead of one person, our Next.js agency template guide covers that version, with priced packages and an inquiry form that asks for a budget.

Components in this post

  • Portfolio Page

    A React portfolio template for designers and developers: hero, case studies in a sheet, experience, priced services, writing and contact.

    Pro
  • Timeline Section

    A marketing section that tells the company story as milestones on a vertical rail, a zigzag around a center line or a horizontal scroller.

    Pro
  • Testimonials Section

    Customer quotes with people, ratings and results: a grid, featured quote, marquee, carousel, wall of love or one large quote.

    Pro
  • Sheet

    A React sheet that slides in from any edge on a spring for filters, a cart, an inspector or phone navigation, swipeable to close on touch.

    Free
  • Contact Page

    A React contact page template: a validated form, copyable channels with live office hours, offices on a drawn map, a short FAQ and logos.

    Free

FAQ

What should a developer portfolio include?

Three to five case studies with the problem, your role, the process and a result you can back up, a short experience list, a way to hire you and a contact form. A list of every repository you ever pushed is a resume dump, and most visitors will not read it.

Is there a free Next.js portfolio template?

Yes. The GitHub topic page for nextjs-portfolio-template listed 25 public repositories on November 6, 2026, and Vercel's template gallery has a free Portfolio Starter Kit. Wingo UI's Portfolio Page is a Pro item; its Sheet and Contact Page are free.

Should a portfolio show prices?

For freelance work, starting prices filter out bad fits before the first call and make the contact form easier to send. If you are looking for a job, hide the services section, because a price list reads as a sales page to a hiring manager.

How should case studies work on a phone?

Open them in a bottom sheet the visitor can swipe away, so they keep their place in the page. Portfolio Page uses a sheet from the right on desktop and a bottom sheet below 768px, with previous and next buttons to step through projects.

Is this a shadcn portfolio template?

It installs the shadcn way: npx wingo-ui@latest add portfolio-page copies the source into your components folder, built on Radix, Tailwind CSS v4 and motion. You own and edit the files, and the CLI can merge later updates into your edits.

Try it in your project

Portfolio Page

Pro

A React portfolio template for designers and developers: hero, case studies in a sheet, experience, priced services, writing and contact.

75 components free, no account needed. Pro from $8 a month.

Open Portfolio Page liveSee pricing
  • Next.js
  • Portfolio
  • Templates
  • Tailwind CSS
  • Freelancing

Share

SR

About the author

Serban Rusu

Founder of Wingo UI

Serban Rusu founded Wingo UI. He builds its component library, CLI and MCP server, and writes about React interfaces for phones and AI coding agents.

More from Serban
Keep reading

Related posts

All posts
Build it: screens and apps

Nextjs Agency Template That Turns Visitors Into Briefs

A nextjs agency template built to collect project briefs: rotating hero, services, case studies, process, team, packages and an inquiry form with budgets.

SRSerban Rusu·Nov 6, 2026·8 min read
Build it: screens and apps

Nextjs Contact Form That Sends Email and Blocks Spam

Build a nextjs contact form in the App Router: zod validation, a Server Action that sends the email, a honeypot, a rate limit and a free contact page template.

SRSerban Rusu·Nov 5, 2026·10 min read
Build it: screens and apps

SaaS Website Template: The 7 Pages Beyond the Home Page

A SaaS website template needs more than a home page. What pricing, about, careers, changelog, contact, legal and compare pages must hold, built in Next.js.

SRSerban Rusu·Nov 5, 2026·20 min read
Newsletter

Get new posts by email

New posts, product news and occasional offers from Wingo UI. We count clicks to keep it relevant.

No spam. Unsubscribe at any time.

WingoUI

Animated, configurable, mobile-first React components and blocks. Copy the source, make it yours, and let your coding agent build with it.

Product

  • Components
  • Templates
  • Pricing
  • AI agents
  • For teams
  • Theme generator

Resources

  • Docs
  • Contact
  • Programs
  • Blog
  • Changelog
  • Changelog RSS
  • llms.txt

Compare

  • Wingo UI vs paid kits
  • Wingo UI vs shadcn/ui
  • shadcn/ui alternatives
  • MUI alternatives
  • Tailwind Plus alternatives
  • Magic UI alternatives
  • Free components

Legal

  • Terms
  • Privacy
  • Refunds
  • License
  • Imprint

Component categories

  • Buttons & Actions
  • Inputs
  • Forms
  • Navigation
  • Overlays
  • Feedback
  • Data Display
  • Tables & Lists
  • Charts & Stats
  • Layout
  • Media
  • AI Kit
  • Text & Effects
  • Mobile
  • Commerce
  • Marketing Sections
  • Blocks
  • Hooks & Utilities

© 2026 Wingo UI

Wingo UI