Use Cases (Updated: 6/2/2026)

How to Build a Portfolio Site with Claude Code and Astro

Build a portfolio site with Claude Code and Astro: copy-paste code, SEO checks, pitfalls, use cases, and launch review.

How to Build a Portfolio Site with Claude Code and Astro

Start with the job your portfolio must do

A portfolio site is not just a gallery. It is a decision page for recruiters, clients, conference organizers, collaborators, and people who are trying to understand whether you can solve their problem. The first version does not need a cinematic animation system. It needs a clear role, a short explanation of your strengths, believable projects, a way to contact you, and enough proof that the visitor can move forward.

In this guide, Claude Code is used as a local development partner, not as a magic design generator. The Claude Code overview describes it as a coding tool that can read a codebase, edit files, run commands, and work with development tools. For beginners, that matters because the workflow is visible: ask for a plan, review the files, apply the change, run the build, and ask for a focused review.

The implementation uses Astro with plain CSS. Astro is a good fit for a portfolio because most of the page can be static HTML generated at build time. That keeps hosting simple and avoids pulling in a client-side framework before you actually need one. The official Astro project structure is also easy for Claude Code to inspect because the important folders are predictable.

If Claude Code itself is still new to you, read the internal Claude Code getting started guide first. For Astro-specific patterns, keep building Astro sites with Claude Code open while you work.

The site we will build

The target is a one-page portfolio with five sections: Hero, About, Projects, Services, and Contact. That is enough for a first public version. A blog, newsletter, CMS, or contact API can be added later, but adding them on day one creates more files, more decisions, and more review surface than a beginner needs.

flowchart TD
  A["Portfolio brief"] --> B["src/data/profile.ts"]
  B --> C["src/pages/index.astro"]
  C --> D["src/styles/global.css"]
  D --> E["npm run build"]
  E --> F["Launch review"]

Keep the data separate from the page. Put the profile and project list in src/data/profile.ts, and let src/pages/index.astro focus on rendering. This separation gives Claude Code a smaller target when you later say, “Improve the project copy only” or “Change the layout without touching the data.”

my-portfolio/
  package.json
  astro.config.mjs
  src/
    data/
      profile.ts
    pages/
      index.astro
    styles/
      global.css
  public/
    ogp.png
    projects/
      task-dashboard.webp
      booking-site.webp

Images live in public/, so /projects/task-dashboard.webp works as a public URL. Follow MDN’s img element reference: use useful alt text and set dimensions where possible. For below-the-fold screenshots, MDN’s lazy loading guide explains the native loading="lazy" attribute. Do not lazy-load the main visual if it is part of the first viewport, because that can hurt perceived loading.

A practical Claude Code prompt

Weak prompts produce weak portfolios. “Make me a cool portfolio” gives Claude Code too much room to invent. A better prompt defines the audience, files, constraints, and completion criteria. Ask Claude Code to explain the plan before editing so you can catch bad assumptions early.

I want to build a personal portfolio site with Astro.
The audience is recruiters and small-business clients who need to understand my work within three minutes.

Requirements:
- One page with Hero, About, Projects, Services, and Contact sections
- Store profile and project data in src/data/profile.ts
- Implement src/pages/index.astro and src/styles/global.css
- Do not add React or a heavy UI library
- Add alt text to every image
- Make CTA buttons readable and wrapped on mobile
- Finish by running npm run build

Before editing, summarize the implementation plan and the files you expect to change.

This prompt also creates a review boundary. If Claude Code wants to add a CMS, animation package, or API route, you can reject that as out of scope. The goal is not to prevent creativity; it is to make the first version launchable. For a deeper SEO pass after the first build, use the internal SEO optimization with Claude Code guide.

Create the project and profile data

For a new project, create an Astro app and start the dev server. If you are working in an existing repository, do not run the create command. Instead, ask Claude Code to inspect the current structure and identify the right files.

npm create astro@latest my-portfolio
cd my-portfolio
npm run dev

Now add the profile file. The example below is intentionally concrete. Replace the name, email, links, screenshots, and project descriptions with your own work. Avoid fake metrics unless you can explain how they were measured.

// src/data/profile.ts
export const profile = {
  name: "Masa Tanaka",
  role: "Frontend engineer / Claude Code implementation partner",
  location: "Tokyo, Japan",
  summary:
    "I build fast, maintainable web experiences with Astro, React, TypeScript, and practical content operations. My focus is small improvements that can be shipped, measured, and repeated.",
  skills: ["Astro", "TypeScript", "React", "CSS", "SEO", "Content Ops"],
  links: {
    email: "masa@example.com",
    github: "https://github.com/example",
    x: "https://x.com/example",
  },
} as const;

export const projects = [
  {
    title: "Booking site conversion cleanup",
    description:
      "Reworked the mobile menu, service explanation, and reservation CTA so visitors could choose the next step without hunting through the page.",
    image: "/projects/booking-site.webp",
    alt: "Screenshot of a booking website with clear service cards and a reservation button",
    tags: ["Astro", "SEO", "Responsive"],
    url: "https://example.com/booking",
  },
  {
    title: "SaaS task dashboard",
    description:
      "Redesigned cards, status filters, and empty states for a task dashboard used by first-time trial users.",
    image: "/projects/task-dashboard.webp",
    alt: "Screenshot of a SaaS dashboard with task cards and status filters",
    tags: ["TypeScript", "UI Design", "Dashboard"],
    url: "https://example.com/dashboard",
  },
  {
    title: "Technical blog rebuild",
    description:
      "Created article templates, internal link rules, and OGP image operations so the site could be updated consistently.",
    image: "/projects/blog-renewal.webp",
    alt: "Screenshot of a technical blog with category navigation and article cards",
    tags: ["Astro", "Content", "Performance"],
    url: "https://example.com/blog",
  },
] as const;

Do not use skill bars unless they carry real meaning. A recruiter cannot verify “React 90%.” A client can understand “redesigned a booking path” or “rebuilt a blog workflow.” Claude Code can help rewrite each project using four fields: problem, role, implementation, and result.

Implement the Astro page

This index.astro file is a minimal but complete page. It uses a mail link for contact because a form backend is not required for the first version. Once the site receives real traffic, you can add a form endpoint, spam protection, and analytics as separate tasks.

---
import { profile, projects } from "../data/profile";
import "../styles/global.css";
---

<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>{profile.name} | Portfolio</title>
    <meta name="description" content={`${profile.role} portfolio with selected projects, services, and contact details.`} />
    <meta property="og:title" content={`${profile.name} | Portfolio`} />
    <meta property="og:description" content={profile.summary} />
    <meta property="og:image" content="/ogp.png" />
  </head>
  <body>
    <main>
      <section class="hero" aria-labelledby="hero-title">
        <p class="eyebrow">{profile.location}</p>
        <h1 id="hero-title">{profile.name}</h1>
        <p class="lead">{profile.summary}</p>
        <div class="actions">
          <a class="button primary" href="#projects">View projects</a>
          <a class="button secondary" href={`mailto:${profile.links.email}`}>Start a conversation</a>
        </div>
      </section>

      <section class="section" aria-labelledby="about-title">
        <h2 id="about-title">What I do</h2>
        <p>{profile.role}: planning, implementation, review, and launch support for focused web projects.</p>
        <ul class="skills">
          {profile.skills.map((skill) => <li>{skill}</li>)}
        </ul>
      </section>

      <section id="projects" class="section" aria-labelledby="projects-title">
        <h2 id="projects-title">Projects</h2>
        <div class="project-grid">
          {projects.map((project) => (
            <article class="project-card">
              <img src={project.image} alt={project.alt} width="960" height="540" loading="lazy" />
              <div class="project-body">
                <h3>{project.title}</h3>
                <p>{project.description}</p>
                <ul class="tag-list">
                  {project.tags.map((tag) => <li>{tag}</li>)}
                </ul>
                <a href={project.url} target="_blank" rel="noreferrer">Read more</a>
              </div>
            </article>
          ))}
        </div>
      </section>

      <section class="section contact" aria-labelledby="contact-title">
        <h2 id="contact-title">Contact</h2>
        <p>For site builds, portfolio reviews, and Claude Code adoption support, send a short email with context.</p>
        <a class="button primary" href={`mailto:${profile.links.email}`}>{profile.links.email}</a>
      </section>
    </main>
  </body>
</html>

The heading structure is intentional: one h1, section-level h2 headings, and project-level h3 headings. That makes the page easier to scan and easier to review with assistive technology. For more detail, pair this guide with responsive design using Claude Code and the internal accessibility article.

Add plain CSS first

The first version should be boring in the right ways: readable text, stable spacing, responsive cards, clear buttons, and no layout shifts. Plain CSS is enough. Add animation later only if it supports the message.

/* src/styles/global.css */
:root {
  color-scheme: light;
  --bg: #f7f3ec;
  --panel: #ffffff;
  --text: #1f2933;
  --muted: #5f6c7b;
  --accent: #0f766e;
  --accent-dark: #115e59;
  --line: #d8dee4;
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: Inter, system-ui, sans-serif;
  background: var(--bg);
  color: var(--text);
  line-height: 1.75;
}

a {
  color: var(--accent-dark);
}

.hero,
.section {
  width: min(1120px, calc(100% - 32px));
  margin: 0 auto;
}

.hero {
  min-height: 82vh;
  display: grid;
  align-content: center;
  padding: 80px 0 56px;
}

.eyebrow {
  color: var(--accent-dark);
  font-weight: 700;
  margin: 0 0 12px;
}

h1 {
  font-size: clamp(2.6rem, 7vw, 5.8rem);
  line-height: 1;
  margin: 0 0 24px;
}

h2 {
  font-size: clamp(1.8rem, 4vw, 3rem);
  margin: 0 0 20px;
}

.lead {
  max-width: 760px;
  color: var(--muted);
  font-size: 1.2rem;
}

.actions,
.skills,
.tag-list {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  padding: 0;
  list-style: none;
}

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 0 18px;
  border: 1px solid var(--accent);
  border-radius: 6px;
  text-decoration: none;
  font-weight: 700;
}

.button.primary {
  background: var(--accent);
  color: white;
}

.button.secondary {
  background: transparent;
  color: var(--accent-dark);
}

.section {
  padding: 64px 0;
}

.skills li,
.tag-list li {
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 6px 12px;
  background: var(--panel);
}

.project-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 24px;
}

.project-card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 8px;
  overflow: hidden;
}

.project-card img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}

.project-body {
  padding: 20px;
}

.contact {
  border-top: 1px solid var(--line);
}

@media (max-width: 640px) {
  .hero {
    min-height: auto;
    padding-top: 56px;
  }

  .button {
    width: 100%;
  }
}

This palette avoids the common AI-product look of a dark purple gradient. It also avoids beige-only monotony by using a teal action color, strong text contrast, and white project panels. The site should feel like a working portfolio, not a landing page template.

Use cases worth designing for

Your portfolio should change slightly depending on who is reading it. Claude Code can help create variants, but only if you name the scenario.

Use caseWhat the reader wantsWhat the page should include
Job searchTechnical range, role clarity, evidenceStack, responsibility, team size, GitHub or demo links
Freelance client workProblem fit and easy contactServices, common requests, email CTA, delivery examples
Speaking or writingTopic authority and biographyTalk topics, article links, short bio, profile photo
Learning logProgress and reproducible projectsNotes, demos, failed attempts, next improvements

For job search, write the project card around your responsibility. For client work, make the next step obvious: review my existing site, improve a landing page, audit a Claude Code workflow, or build a small static site. For speaking, the hero should show the topic area quickly and link to talks or articles.

ClaudeCodeLab uses this same thinking for monetization. A portfolio can connect free educational content, a downloadable checklist, paid templates, and consulting. The CTA should not be a vague “contact me” at the end of the page. It should explain what kind of conversation is useful. See the training and consultation page for a concrete example of turning content into a paid path without hiding the value of the article.

SEO, images, and accessibility checks

SEO for a portfolio is mostly clarity. Use the words your audience would search: “frontend engineer portfolio,” “Astro developer,” “Claude Code implementation support,” or a local service keyword if location matters. Put those words naturally in the title, description, headings, and project copy. Do not stuff them into every paragraph.

Ask Claude Code for a focused review after the page renders:

Review this Astro portfolio for SEO and accessibility.
Check:
- Whether the title and description are specific
- Whether h1, h2, and h3 are ordered correctly
- Whether image alt text explains the content
- Whether internal and external links feel natural
- Whether the primary CTA appears near the top and bottom
- Whether buttons and cards remain readable on mobile
Return only the issues, the reason, and the smallest useful fix.

Images should prove the work. If you do not have permission to show real client screens, use redacted screenshots, small mockups, diagrams, or before-and-after tables. Never paste private client data into Claude Code. Replace names, revenue numbers, unreleased URLs, and .env values with safe placeholders.

Common pitfalls

The first pitfall is overbuilding. A portfolio with a 3D scene, motion library, custom CMS, analytics stack, and contact API can still fail if the first screen does not say who you are and what you do. Launch a readable static version first.

The second pitfall is vague proof. “I know React” is weak. “I redesigned the booking path so mobile visitors saw the reservation action earlier” is stronger. Ask Claude Code to rewrite every project card into problem, role, implementation, and result.

The third pitfall is skipping launch checks. Broken links, missing OGP images, unhelpful alt text, and mobile buttons that overflow are common on beginner sites. Run npm run build, inspect the first viewport, open each project link, and check the contact link before publishing.

The fourth pitfall is letting AI change the scope silently. If Claude Code adds packages or files that were not in the plan, stop and ask why. A small portfolio should stay small until there is a real reason to expand it.

Tested result and next step

The structure in this article is designed around Astro’s standard folders and simple browser features, so the code remains easy to review. In practice, the most useful decision is moving portfolio data into src/data/profile.ts. It keeps content editing separate from layout editing, which makes Claude Code reviews shorter and safer.

For a real launch, split the work into four Claude Code sessions: brief and file plan, implementation, SEO/accessibility review, and release checklist. That rhythm catches more issues than one giant “build everything” prompt. Once the page is live, keep improving it: add better project screenshots, rewrite weak case studies, and adjust the CTA based on the conversations you actually want.

#Claude Code #portfolio #Astro #CSS #SEO
Free

Free PDF: Claude Code Cheatsheet

Enter your email and download the one-page Claude Code cheatsheet for commands, review habits, and safe workflows.

We handle your data with care and never send spam.

Level up your Claude Code workflow

Start with the free PDF, use Gumroad guides when you need repeatable workflows, and book consultation when rollout or revenue paths need human judgment.

Masa

About the Author

Masa

Engineer focused on practical Claude Code workflows. Runs claudecode-lab.com, a 10-language technical media site.