Personal portfolio site for Giancarlo Papa — Senior Full Stack Engineer.
Stack: Nuxt 4 · Nuxt UI v4 · Nuxt Content v3 · Cal.com · Resend · Vercel
| Route | Description |
|---|---|
/ |
Homepage — hero, about, services |
/resume |
Full CV rendered from JSON Resume |
/blog |
Blog listing (Markdown via @nuxt/content) |
/blog/[slug] |
Individual post with prose styling |
/book |
Cal.com booking flow |
/contact |
Contact form (Resend) |
/skillmatrix |
Tech skills with proficiency levels |
/colophon |
About this site |
npm install
npm run devCreate a .env file in the project root:
# Cal.com booking integration
CAL_API_KEY=cal_live_...
CAL_USERNAME=your-cal-username
CAL_BASE_URL=https://api.cal.com
# Site
SITE_URL=https://giancarlopapa.com
CONTACT_EMAIL=hello@giancarlopapa.com
# Contact form (resend.com)
RESEND_API_KEY=re_...
RESEND_TO_EMAIL=giancarlo.papa@gmail.comnpm run dev # Start dev server
npm run build # Production build
npm run preview # Preview production build locally
npm run generate-pdf # Regenerate static PDF resume (also runs pre-commit via Husky)
npm run typecheck # TypeScript check
npm run lint # ESLint
npm run deploy # Deploy to Vercel (prod)The resume is available as a downloadable PDF generated from content/giancarlo_papa_resume.json.
- Static PDF (
public/giancarlo_papa_resume.pdf): generated byscripts/generate-pdf.mjs, auto-runs pre-commit via Husky when the JSON is staged. - Dynamic PDF (
/api/resume/pdf): server route (server/api/resume/pdf.get.ts) that renders on demand — same HTML/CSS as the script. - Requires
CHROMIUM_EXECUTABLE_PATHin.envpointing to a local Chrome binary. - Style reference:
public/giancarlo_papa_vitae.pdf— the design bible for fonts, icons, colors, and layout.
Blog posts live in content/blog/*.md with this frontmatter:
---
title: "Post title"
description: "Short description"
date: "2026-02-22"
tags: ["tag1", "tag2"]
draft: false
---The resume is content/giancarlo_papa_resume.json (JSON Resume format).
Sends email via Resend — server/api/contact.post.ts.
The from address is noreply@giancarlopapa.com. Verify the domain at resend.com/domains before going to production. For local testing use onboarding@resend.dev.
Cal.com integration in server/utils/cal.ts and server/api/cal/:
- Event types:
GET /v1/event-types - Available slots:
GET /v2/slots(cal-api-version: 2024-09-04) - Create booking:
POST /v2/bookings(cal-api-version: 2024-08-13)
Hosted on Vercel. Nitro preset: vercel.
Deployment is fully automated via GitHub Actions (.github/workflows/deploy.yml):
git push origin main → GitHub Actions builds + deploys to Vercel
Set all env vars in GitHub → Settings → Secrets (for CI build) and in Vercel → Settings → Environment Variables (for runtime).
app/
pages/ # index.vue, resume.vue, book.vue, contact.vue, blog/, ...
components/cv/ # CV section components
composables/ # useCalBooking.ts, useProfileData.ts, useResumeContent.ts
data/ # Static profile data
types/ # TypeScript types (cal.ts, resume.ts)
assets/css/ # Tailwind theme, Snazzy colours, typography
server/
api/ # contact.post.ts, profile.get.ts, cal/
utils/cal.ts # Cal.com API client
content/
blog/ # Markdown blog posts
giancarlo_papa_resume.json
content.config.ts # @nuxt/content collection schema