Back to portfolio

Hackyfolio

Documentation

A JSON-driven personal portfolio built with Next.js. Write your content in one file, and the page builds itself. No component editing required.

One-paste setup

The fastest way to build your own: open your AI coding tool (Claude Code, Cursor, or similar), paste the prompt below, and follow along. It clones the repo, installs it, asks you for your details, writes your portfolio.json, and verifies the build. You just answer its questions.

Setup prompt
You are helping me build my personal portfolio website from an open-source template called Hackyfolio. It is a Next.js site where ALL content lives in ONE JSON file (app/data/portfolio.json) and fixed components render it. I should only ever edit that JSON, never the component code.

Work through these steps in order:

1) Set up the project:
   - git clone https://github.com/PythonHacker24/yo-hackyfolio.git my-portfolio
   - cd my-portfolio
   - npm install
   - npm run dev  (then tell me to open http://localhost:3000)

2) Learn the schema BEFORE editing anything. Read:
   - CLAUDE.md (the full schema guide)
   - app/data/portfolio.json (current example content)
   - the exported *Data types at the top of each file in app/components/sections/

3) Collect MY real information. Ask me to paste my resume, LinkedIn, or any notes, and parse whatever I give you. I will need to provide:
   - name, role/title, short bio, location and timezone
   - work experience (companies, roles, dates, what I did)
   - skills / tech stack
   - projects, education, publications, YouTube or other links (only what applies)
   - social links (GitHub, LinkedIn, X, etc.), email, calendar link
   - a profile photo (tell me to drop it into the public/ folder)
   Ask follow-up questions only for missing essentials. Do not invent facts.

4) Rewrite app/data/portfolio.json using ONLY my real information:
   - keep the same schema and the same section "type" values
   - delete sections I have no data for; reorder them if I want a different order
   - rich text uses the Block format: a string is a paragraph, {"list": [...]} is bullets, and **bold** + [text](url) work inline
   - update the top-level "meta" (siteUrl, calendarUrl, email) and "socials"

5) Verify:
   - run npm run build and fix any type error or missing field it reports
   - confirm the site looks right at http://localhost:3000

6) When done, summarize what changed and walk me through deploying to Vercel (push to GitHub, import the repo at vercel.com, click Deploy).

Rules: app/data/portfolio.json is the single source of truth. Do not hardcode content in components, do not add a second data file, and ask before deleting anything you are unsure about. Start now with step 1.

Tip: have your resume or LinkedIn handy. Paste it when the assistant asks, and it will structure everything for you.

What is this

Most portfolios mix content and code, so changing a job title means digging through JSX. Hackyfolio splits them apart. All your content lives in a single data file. A set of fixed components reads that file and renders the page.

The result: you edit text, not code. And it ships with two views.

  • Human mode — the normal, good-looking website.
  • Agent mode — the same content as plain Markdown, generated from the same data, friendly for AI agents and scrapers. Toggle it with the switch in the bottom bar.

Quick start

You need Node.js 18 or newer. Clone the repo from GitHub, then install and run:

terminal
# clone the repo
git clone https://github.com/PythonHacker24/yo-hackyfolio.git
cd yo-hackyfolio

# install dependencies
npm install

# run the local dev server
npm run dev

Open http://localhost:3000 and edit away. The page reloads as you save. To build the production version:

terminal
npm run build
npm run start

Tip: the fastest way to fill this in is to hand your resume to an AI coding agent like Claude Code and ask it to populate the data file. The repo includes a CLAUDE.md that teaches agents the schema.

The core idea

One file drives everything. The flow looks like this:

app/data/portfolio.json
  -> SectionRenderer  (picks a component by "type")
    -> one component per section type
      -> rendered on the page

The page loops over an ordered sections array and renders each one. The order in the file is the order on the page. The agent-mode Markdown view is generated from the same JSON, so the two views can never drift apart.

The data file

Everything lives in app/data/portfolio.json. It has three top-level parts:

app/data/portfolio.json
{
  "meta":     { "siteUrl": "...", "calendarUrl": "...", "email": "..." },
  "socials":  [ { "label": "GitHub", "href": "...", "icon": "github" } ],
  "sections": [ /* ordered list of sections, see below */ ]
}

Each section is a small object with a type, a title, and its data:

a section
{
  "type": "experience",
  "title": "Experience",
  "data": { /* shape depends on the type */ }
}

The hero section is the only one without a title.

Section types

These are the built-in types. Each maps to a component in app/components/sections/. Open a component to see its exact *Data type at the top of the file.

typeWhat it shows
heroPhoto, name, pronunciation, live local time, intro lines.
experienceA featured current role plus a clickable 'Previously' list.
techStackSkills as a scrolling row that expands into categories.
expandableCardA single titled card with a read-more body.
projectA project with description, a stats grid, and a link.
youtubeA channel header with a list of videos.
educationA list of schools or courses.
githubA GitHub contributions graph (give it a username).
publicationsPapers with an abstract you can expand.
recommendationsQuotes from people, with name and role.
contactCall-to-action buttons and your social links.

A typical section, the project type, looks like this:

a project section
{
  "type": "project",
  "title": "Coolest Experiment",
  "data": {
    "name": "Double Dart",
    "link": "https://example.com",
    "subtitle": "a short tagline",
    "body": [ "A paragraph with **bold** and a [link](https://x.com)." ],
    "stats": [ { "value": "201", "label": "sign-ups in 3 days" } ],
    "footerLink": { "label": "Read more", "url": "https://example.com" }
  }
}

Rich text (the Block type)

Anywhere you see a body or intro field, the value is an array of “blocks”. A block is either a paragraph (a plain string) or a bullet list (an object with a list):

body example
"body": [
  "A paragraph. Supports **bold** and [links](https://example.com).",
  { "list": ["First bullet", "Second bullet with **bold**"] },
  "Another paragraph after the list."
]

Inline formatting works inside any string: **bold** and [text](url). That is the whole syntax.

Socials and icons

The socials array is used in two places at once: the bottom navbar and the contact section. Edit it once, both update.

a social link
{ "label": "GitHub", "href": "https://github.com/you", "icon": "github" }

Valid icon names: github, linkedin, x, youtube, discord, medium, calendar, mail. They map to real icons in app/components/icons.tsx — add more there if you need them.

Rearrange or remove sections

Want GitHub above Education? Move its block up in the sections array. The page renders in array order, so reordering the JSON reorders the page.

To remove a section, delete its block. To hide one temporarily, cut it and paste it back later. No code touched either way.

Add a new section type

Only needed if none of the built-in types fit. Four steps:

1. Create app/components/sections/AwardsSection.tsx
   -> export the component AND its AwardsData interface

2. In app/components/sections/registry.tsx
   -> add the variant to the Section union

3. In the same file's SectionRenderer
   -> add a case for "awards"

4. Add a block with "type": "awards" to portfolio.json

If you forget step 3, TypeScript errors at the SectionRenderer switch, so it is hard to get wrong. Run npm run build to confirm.

Deploy

The smoothest host is Vercel, by the team behind Next.js. It is free for personal sites.

  1. Push your code to a GitHub repository.
  2. On Vercel, click New Project and import that repo.
  3. Accept the defaults and click Deploy.

After that, every push to GitHub triggers a rebuild and redeploy. Add a custom domain under the project's Domains settings. Any Next.js host works too (Netlify, Cloudflare, your own server), but Vercel is the easiest path.