A modern, SEO-optimized portfolio website showcasing professional experience, projects, speaking engagements, and education.
π Live Site: navarrolajous.com | www.navarrolajous.com
- Overview
- β¨ Features
- π οΈ Tech Stack
- π Project Structure
- ποΈ Data Architecture
- π Getting Started
- π» Development Workflow
- β Adding Content
- π¨ Customization
- π Available Scripts
- π Deployment
- π€ Contributing
- π License
- π§ Contact
This is a professional portfolio website built with Next.js 14 (App Router) and TypeScript, featuring a modern design system with dark mode support, comprehensive SEO optimization, and dynamic content sections. The site showcases professional experience, project portfolio, academic background, and speaking engagements at conferences.
Key Highlights:
- π± Fully responsive design (mobile-first approach)
- π Dark/Light theme with system detection
- π SEO optimized with sitemap, robots.txt, and Schema.org structured data
- β‘ Static generation for optimal performance
- π Integrated Umami Analytics
- π§ Contact form with Resend email integration
- π€ Speaking engagements and conference talks showcase
- Jobs & Startups: Separate tabs for employment history and entrepreneurial ventures
- Detail Pages: Individual pages for each role with dynamic routes (
/experience/[slug]) - Rich Content: Responsibilities, achievements, tech stack, location, and period
- Company Links: Direct links to company websites and profiles
- Three Categories: Freelance, Hobby, and Open Source projects
- Detail Pages: Comprehensive project showcase (
/projects/[slug]) - Project Details: Features, challenges, impact, screenshots, and banners
- Links: GitHub repositories and live project URLs
- Visual Content: Project banners and multiple screenshots
- Conference Presentations: Showcase of talks at DuneCon, DeFi Security Summit, etc.
- Detail Pages: Full talk information with descriptions and topics (
/talks/[slug]) - Media Integration: YouTube video embeds and PDF slide viewers
- Event Information: Date, location, and event details
- Academic Background: Degrees and certifications
- Detail Pages: In-depth educational credentials (
/education/[slug]) - Academic Details: Thesis information, coursework, achievements, and tech stack
- Contact Form: Validated form with Zod schema
- Email Integration: Automated email notifications via Resend API
- User Feedback: Toast notifications for form submission status
- π Resume Download: Direct PDF download from header navigation
- π Social Links: GitHub and LinkedIn integration
- π¨ Theme Toggle: Smooth dark/light mode switching with persistence
- π± Mobile Navigation: Responsive header with mobile menu
- π SEO: Meta tags, OpenGraph, Twitter cards, and JSON-LD structured data
- πΊοΈ Sitemap: Auto-generated sitemap for all routes
- π€ Robots.txt: Search engine crawler configuration
- π Analytics: Umami Analytics integration for privacy-friendly tracking
- Next.js 14 - React framework with App Router
- React 18 - UI library
- TypeScript 5 - Type-safe JavaScript (strict mode)
- Tailwind CSS 3 - Utility-first CSS framework
- tailwindcss-animate - Animation utilities
- tailwind-merge - Utility for merging Tailwind classes
- shadcn/ui - Re-usable component library
- Radix UI - Unstyled accessible components
- Dialog, Dropdown Menu, Label, Navigation Menu, Slot, Tabs, Toast, Avatar
- Lucide React - Icon library
- class-variance-authority - CVA for component variants
- next-themes - Dark mode implementation
- react-hook-form - Form handling
- Zod - Schema validation
- @hookform/resolvers - Form validation resolvers
- Resend - Email API for contact form
- clsx - Conditional classNames utility
- ESLint - Code linting
- PostCSS - CSS processing
- Autoprefixer - CSS vendor prefixes
- Vercel - Hosting and deployment platform
- Umami Analytics - Privacy-friendly analytics
website/
βββ app/ # Next.js App Router
β βββ api/
β β βββ contact/
β β βββ route.ts # Contact form API endpoint
β βββ contact/
β β βββ components/
β β β βββ ContactForm.tsx # Contact form component
β β βββ page.tsx # Contact page
β βββ education/
β β βββ [slug]/
β β β βββ page.tsx # Education detail pages
β β βββ components/
β β β βββ EducationCard.tsx
β β βββ page.tsx # Education list page
β βββ experience/
β β βββ [slug]/
β β β βββ page.tsx # Experience detail pages
β β βββ components/
β β β βββ ExperienceCard/
β β β βββ ExperiencesTab.tsx
β β βββ layout.tsx # Experience layout for metadata
β β βββ page.tsx # Experience list with tabs
β βββ projects/
β β βββ [slug]/
β β β βββ page.tsx # Project detail pages
β β βββ components/
β β β βββ ProjectCard/
β β β βββ ProjectTabs.tsx
β β βββ layout.tsx # Projects layout for metadata
β β βββ page.tsx # Projects list with tabs
β βββ talks/
β β βββ [slug]/
β β β βββ page.tsx # Talk detail pages
β β βββ components/
β β β βββ PDFViewer.tsx
β β β βββ TalkCard.tsx
β β β βββ YouTubeEmbed.tsx
β β βββ page.tsx # Talks list page
β βββ favicon.ico
β βββ globals.css # Global styles
β βββ layout.tsx # Root layout
β βββ page.tsx # Home page
β βββ robots.ts # Robots.txt generation
β βββ sitemap.ts # Sitemap generation
β
βββ components/ # Shared components
β βββ emails-templates/ # Email templates for Resend
β βββ Footer/
β βββ Header/ # Navigation header
β βββ ModeToggle/ # Dark/light theme toggle
β βββ SchemaOrgScripts/ # SEO structured data
β βββ ui/ # shadcn/ui components
β βββ theme-provider.tsx # Theme provider wrapper
β
βββ constants/ # Application constants
β βββ routes.ts # Route definitions and SITE_URL config
β
βββ domains/ # TypeScript interfaces/types
β βββ Education.ts # Education interface
β βββ Experience.ts # Experience interface
β βββ Project.ts # Project interface
β βββ Talk.ts # Talk interface
β
βββ lib/ # Library utilities
β βββ utils.ts # Tailwind utility (cn function)
β
βββ public/ # Static assets
β βββ assets/ # Images, banners, etc.
β βββ resume.pdf # Downloadable resume
β
βββ services/ # Data services (static data)
β βββ education.ts # Education data
β βββ experience.ts # Jobs, startups, skills data
β βββ projects.ts # Projects data (freelance, hobby, opensource)
β βββ talks.ts # Talks and presentations data
β
βββ utils/ # Utility functions
β βββ getEnv.ts # Environment variable helper
β βββ iconUtils.ts # Icon utilities
β
βββ .claude/ # Claude Code configuration
β βββ commands/ # Custom slash commands
β β βββ start.md # /start - Create feature branch
β β βββ commit.md # /commit - Stage and commit
β β βββ sync.md # /sync - Sync with main
β β βββ build.md # /build - Build and validate
β β βββ release.md # /release - Create release
β β βββ dev.md # /dev - Start dev server
β β βββ finish.md # /finish - Create PR
β β βββ release-notes.md # /release-notes - Enhance release
β βββ CLAUDE.md # AI assistant guidance
β
βββ .env.template # Environment variables template
βββ .nvmrc # Node version specification
βββ components.json # shadcn/ui configuration
βββ next.config.js # Next.js configuration
βββ package.json # Dependencies and scripts
βββ postcss.config.js # PostCSS configuration
βββ tailwind.config.ts # Tailwind configuration
βββ tsconfig.json # TypeScript configuration
This project uses a static TypeScript data pattern where all content is stored as typed data in the services/ directory. This approach provides:
- β Type safety for all content
- β Easy content management
- β Fast builds (static generation)
- β No database needed
All content types are defined as TypeScript interfaces in domains/:
// domains/Experience.ts
export interface Experience {
id: number;
slug: string;
type: 'job' | 'startup';
company: string;
position: string;
period: string;
location?: string;
companyUrl?: string;
responsibilities: string[];
achievements?: string[];
technologies: string[];
banner?: string;
}
// domains/Project.ts
export interface Project {
id: number;
slug: string;
type: 'freelance' | 'hobby' | 'opensource';
name: string;
company: string;
github?: string;
website?: string;
period: string;
description: string;
detailedDescription?: string;
features?: string[];
challenges?: string[];
technologies: string[];
banner?: string;
screenshots?: string[];
impact?: string;
}Content is stored in services/ files as typed arrays:
// services/experience.ts
import { Experience } from "@/domains/Experience";
export const jobs: Experience[] = [
{
id: 1,
slug: "webacy",
type: "job",
company: "Webacy",
position: "Staff Software Engineer",
period: "Jan 2023 β Present",
// ... more fields
},
// ... more jobs
];
export const startups: Experience[] = [
// ... startups
];- Node.js: Version specified in
.nvmrc(usenvm useif you have nvm installed) - npm: Comes with Node.js
- Resend Account: For contact form email functionality (sign up at resend.com)
-
Clone the repository:
git clone https://github.com/rlajous/website.git cd website -
Set up Node.js version:
nvm use
If you don't have nvm, ensure you're using the Node.js version from
.nvmrc. -
Install dependencies:
npm install
-
Configure environment variables:
cp .env.template .env.local
Update
.env.localwith your values:# Resend API Key (get from https://resend.com/api-keys) RESEND_API_KEY=re_your_api_key_here # Email recipient for contact form RECIPIENT_EMAIL=[email protected] # Optional: Override site URL for different environments NEXT_PUBLIC_SITE_URL=https://navarrolajous.com
-
Start the development server:
npm run dev
-
Open your browser: Navigate to http://localhost:3000
Issue: Module not found errors
- Solution: Delete
node_modulesand.next, then runnpm installagain
Issue: Environment variables not working
- Solution: Restart the dev server after changing
.env.local - Ensure variables start with
NEXT_PUBLIC_for client-side access
Issue: Build fails
- Solution: Run
npm run buildlocally to see detailed error messages - Check TypeScript errors with
npm run lint
This project uses custom slash commands (in .claude/commands/) for streamlined development:
-
/start- Start a new PR by creating a feature branch- Creates branch following naming convention (
feat/,fix/, etc.) - Stores context for other commands
- Creates branch following naming convention (
-
/commit- Stage and commit changes with proper formatting- Follows conventional commit format
- Adds co-author attribution
-
/sync- Sync your feature branch with latest main- Prevents merge conflicts
- Keeps branch up to date
-
/build- Build and validate the production bundle locally- Runs
npm run build - Validates output
- Runs
-
/dev- Start the development server with environment validation- Checks environment variables
- Starts
npm run dev
-
/finish- Create a pull request with comprehensive description- Generates PR description
- Creates PR on GitHub
-
/release- Create a new release version and trigger deployment- Bumps version (patch/minor/major)
- Creates git tag
- Triggers Vercel deployment
-
/release-notes- Enhance GitHub release with detailed notes- Generates categorized changelog
- Updates GitHub release
Branch Naming Convention:
feat/feature-name # New features
fix/bug-description # Bug fixes
refactor/what-changed # Code improvements
content/what-updated # Content updates
design/what-improved # Design changes
docs/what-documented # Documentation
Commit Message Format:
[ Type ] Description
Examples:
[ Feature ] Add blog section with MDX support
[ Bug ] Fix mobile navigation overflow
[ Content ] Update experience with Webacy role
[ Design ] Improve dark mode theme transitions
[ Refactor ] Simplify contact form validation
- Open
services/experience.ts - Add a new entry to the
jobsorstartupsarray:
export const jobs: Experience[] = [
{
id: 8, // Increment ID
slug: "company-name", // URL-friendly slug
type: "job",
company: "Company Name",
position: "Your Position",
period: "Jan 2024 β Present",
location: "Remote", // Optional
companyUrl: "https://company.com", // Optional
responsibilities: [
"Key responsibility 1",
"Key responsibility 2",
"Key responsibility 3",
],
achievements: [ // Optional
"Achievement 1",
"Achievement 2",
],
technologies: ["React", "TypeScript", "Node.js"],
banner: "/experience/company-banner.jpg", // Optional
},
// ... existing jobs
];- Add banner image to
public/assets/experience/(if using) - The detail page will be auto-generated at
/experience/company-name
- Open
services/projects.ts - Add to
freelance,hobby, oropensourcearray:
export const freelance: Project[] = [
{
id: 8, // Increment ID
slug: "project-name",
type: "freelance",
name: "Project Name",
company: "Client Name",
github: "https://github.com/user/repo", // Optional
website: "https://project.com", // Optional
period: "2024",
description: "Brief description for card view",
detailedDescription: "Longer description for detail page", // Optional
features: [ // Optional
"Feature 1",
"Feature 2",
],
challenges: [ // Optional
"Challenge 1",
"Challenge 2",
],
technologies: ["Next.js", "Tailwind CSS"],
banner: "/projects/project-banner.jpg", // Optional
screenshots: [ // Optional
"/projects/project-screenshot-1.jpg",
"/projects/project-screenshot-2.jpg",
],
impact: "Impact description", // Optional
},
// ... existing projects
];- Add images to
public/assets/projects/ - Detail page will be at
/projects/project-name
- Open
services/talks.ts - Add a new talk:
export const talks: Talk[] = [
{
id: 3, // Increment ID
slug: "event-name-year",
title: "Talk Title",
event: "Conference Name",
location: "City, Country",
date: "2025-03-15",
description: "Talk description and key points",
topics: ["Topic 1", "Topic 2", "Topic 3"],
slides: "https://slides.com/presentation", // Optional
video: "https://youtube.com/watch?v=...", // Optional
banner: "/talks/event-banner.jpg", // Optional
},
// ... existing talks
];- Add banner to
public/assets/talks/ - Detail page will be at
/talks/event-name-year
- Open
services/education.ts - Add new entry:
export const education: Education[] = [
{
id: 3, // Increment ID
slug: "degree-institution",
degree: "Degree Name",
institution: "University Name",
period: "2020 β 2024",
location: "City, Country", // Optional
institutionUrl: "https://university.edu", // Optional
specialization: "Specialization", // Optional
thesis: { // Optional
title: "Thesis Title",
description: "Thesis description",
link: "https://thesis-link.com",
},
coursework: ["Course 1", "Course 2"], // Optional
achievements: ["Achievement 1"], // Optional
technologies: ["Python", "R", "MATLAB"],
banner: "/education/institution-banner.jpg", // Optional
},
// ... existing education
];Edit app/globals.css to modify the color palette:
@layer base {
:root {
--primary: 222.2 47.4% 11.2%; /* Change primary color */
--secondary: 210 40% 96.1%; /* Change secondary color */
/* ... other colors */
}
.dark {
--primary: 210 40% 98%; /* Dark mode primary */
/* ... other colors */
}
}Components follow the shadcn/ui pattern. To modify a component:
- Find it in
components/ui/ - Edit the component file
- Maintain the CVA pattern for variants
- Create a new directory in
app/ - Add
page.tsx:export default function NewPage() { return <div>New Page Content</div> }
- Add to navigation in
components/Header/ - Update
constants/routes.ts
Edit app/layout.tsx for global metadata:
export const metadata: Metadata = {
title: "Your Name | Title",
description: "Your description",
// ... other metadata
};For page-specific metadata, add generateMetadata to page files.
# Development
npm run dev # Start development server (http://localhost:3000)
# Production
npm run build # Build for production
npm start # Start production server
# Code Quality
npm run lint # Run ESLintThis site is optimized for Vercel deployment with automatic CI/CD:
-
Fork/Clone this repository
-
Connect to Vercel:
- Go to vercel.com
- Click "New Project"
- Import your repository
- Vercel will auto-detect Next.js
-
Configure Environment Variables: In Vercel project settings, add:
RESEND_API_KEY=your_resend_api_key [email protected] NEXT_PUBLIC_SITE_URL=https://yourdomain.com -
Configure Domains:
- Add your custom domain in Vercel project settings
- Update DNS records as instructed by Vercel
- Both apex and www domains are supported
-
Deploy:
- Push to
mainbranch to trigger deployment - Vercel builds and deploys automatically
- Preview deployments created for pull requests
- Push to
- Analytics: Configure Umami Analytics in
app/layout.tsx - Sitemap: Automatically generated at
/sitemap.xml - Robots: Configured at
/robots.txt - Monitoring: Check Vercel dashboard for build logs and analytics
Contributions are welcome! Whether you're fixing bugs, adding features, or improving documentation, your help is appreciated.
-
Fork the repository
-
Create a feature branch:
git checkout -b feat/amazing-feature
-
Make your changes:
- Follow the existing code style
- Add types for all TypeScript code
- Test your changes locally
- Update documentation if needed
-
Commit your changes:
git commit -m "[ Feature ] Add amazing feature" -
Push to your fork:
git push origin feat/amazing-feature
-
Open a Pull Request
- TypeScript: Use strict mode, add types for all variables
- Components: Follow shadcn/ui patterns
- Naming: Use descriptive names, camelCase for variables/functions
- Comments: Add comments for complex logic
- Formatting: Use Prettier (if configured) or follow existing style
This project is licensed under the MIT License - see the LICENSE file for details.
Rodrigo Manuel Navarro Lajous
- π Website: navarrolajous.com
- πΌ LinkedIn: linkedin.com/in/rodrigo-lajous
- π GitHub: @rlajous
- π¦ Twitter: @arlequin_eth