Skip to main content
All portfolio content lives in data/ as plain TypeScript modules. There is no CMS, no database, and no runtime fetching — every array is bundled at build time. The single exception is portfolio-context.ts, whose output is consumed by the chat API route at request time.

TypeScript Interfaces (types/index.ts)

A single file exports all four shared interfaces. Both data/ and components/ import from here.
import type { LucideIcon } from "lucide-react";
import type { StaticImageData } from "next/image";

export interface TimelineItem {
  title: string;      // Entry heading (role name or degree title)
  sub_title: string;  // Secondary line (company or institution)
  years: string;      // Date range, e.g. "2022 - 2024"
  details: string;    // Full description paragraph
  japanese?: string;  // Optional Japanese label for the entry
}

export interface Project {
  title: string;          // Display name
  japanese: string;       // Japanese subtitle shown in the card
  description: string;    // One-sentence description
  image: StaticImageData; // Statically imported AVIF (Next.js resolves dimensions)
  tags: string[];         // Technology stack labels
  github: string;         // GitHub repo URL ("#" if private)
  demo: string;           // Live demo URL
}

export interface Skill {
  icon: LucideIcon;   // Lucide icon component reference (not JSX element)
  title: string;      // English category name
  japanese: string;   // Japanese category label
  techs: string[];    // Specific technologies in this category
}

export interface SocialLink {
  image: StaticImageData; // Statically imported icon image
  label: string;          // Platform name, e.g. "GitHub"
  href: string;           // Profile URL
}
image fields in Project and SocialLink use StaticImageData — the type returned by Next.js when you import an image file directly. This lets Next.js automatically include the correct width, height, and blurDataURL without manual configuration.

data/experience.ts

Exports two TimelineItem[] arrays consumed by components/Experience.tsx via components/TimelineSection.tsx.

educations

Three entries covering formal qualifications in reverse chronological order:
TitleInstitutionYears
Desarrollo de Aplicaciones WebIES Hermenegildo Lanz, Granada2022–2024
Sistemas Microinformáticos y RedesIES Padre Suárez, Granada2020–2022
Certificado de Profesionalidad en SMRAcademia Hermanos Naranjo, Granada2018

experiences

Four entries covering work history in reverse chronological order:
TitleCompanyYears
Desarrollador Web FreelanceFreelance2024–Presente
Desarrollador Web en PrácticasATI Soluciones2024
Sistemas Microinformáticos y Redes en PrácticasDelegación Territorial de Medio Ambiente – Junta de Andalucía2022
Técnico en SMR en PrácticasMEGA 2 SEGURIDAD S.L2018
Connected to: Experience.tsxTimelineSection.tsx

data/projects.ts

Exports a single Project[] array (projects) consumed by components/Works.tsx. Contains ten projects:
TitleJapanese labelKey tags
Diversia InteriorismoインテリアデザインNextJS, Prisma, Turso, Cloudflare
Recursos Web開発者リソースAstro, TailwindCSS
Noken Study日本語学習NextJS, TailwindCSS
Review ForgeレビューサイトNextJS, NestJS, Prisma, Neon
Noken Vocabulary日本語学習React, Supabase, React-router
Illustrator AmeliaイラストポートフォリオAstro, TailwindCSS
Roger Civ Devポートフォリオ (Astro)Astro, TailwindCSS
Portfolio NextjsポートフォリオNextJS, TailwindCSS
SaaS BlogブログプラットフォームNextJS, Prisma, Stripe, Supabase
Premium Rental Cars高級車レンタルNextJS, Prisma, Stripe, Uploadthing
Images are statically imported at the top of the file:
import diversiaImage from "@/public/images/projects/diversia.avif";
// ...
export const projects: Project[] = [
  {
    title: "Diversia Interiorismo",
    image: diversiaImage,
    // ...
  },
];
Connected to: Works.tsx

data/skills.ts

Exports a single Skill[] array (skills) consumed by components/Skills.tsx. Contains six skill categories:
TitleJapaneseIconTechnologies
FrontendフロントエンドCodeXmlReact, TypeScript, Next.js, Astro, Tailwind CSS
BackendバックエンドServerNode.js, Express, PHP, Laravel, REST APIs
DatabaseデータベースDatabasePostgreSQL, MongoDB, MySQL, SQLite, Prisma
DesignデザインPaletteFigma, CSS, Responsive Design, UI Components
DevOpsデブオプスGitBranchGit, GitHub Actions, Docker, CI/CD
ToolsツールWrenchVS Code, Postman, Biome, pnpm, Vite
All icons are from the lucide-react package. The icon field stores the component reference itself, not a rendered element:
import { CodeXml, Database } from "lucide-react";

export const skills: Skill[] = [
  {
    icon: CodeXml,  // Component reference, not <CodeXml />
    title: "Frontend",
    japanese: "フロントエンド",
    techs: ["React", "TypeScript", "Next.js", "Astro", "Tailwind CSS"],
  },
];
Inside Skills.tsx, each icon is instantiated as:
const Icon = skill.icon;
return <Icon className="w-7 h-7 text-primary" />;
Connected to: Skills.tsx

data/socials.ts

Exports a single SocialLink[] array (socialLinks) with three entries. Images are statically imported AVIF files from public/images/socials/.
export const socialLinks: SocialLink[] = [
  {
    image: githubImage,
    label: "GitHub",
    href: "https://github.com/rogerciv",
  },
  {
    image: linkedinImage,
    label: "LinkedIn",
    href: "https://linkedin.com/in/rogerciv",
  },
  {
    image: instagramImage,
    label: "Instagram",
    href: "https://instagram.com/tuusuario",
  },
];
Connected to: portfolio-context.ts (used to build the AI system prompt; not rendered directly by a UI component).
data/socials.ts feeds only the AI assistant context. The Footer component manages its own local social link data (Links[] type, PNG sources from public/images/footer/) independently.

data/portfolio-context.ts

Exports a single function generatePortfolioContext(): string that builds the system prompt string used by the chat API route. Imports from: experience.ts, projects.ts, skills.ts, socials.ts Consumed by: app/api/chat/route.ts
export function generatePortfolioContext(): string {
  const skillsText = skills
    .map((skill) => `- ${skill.title}: ${skill.techs.join(", ")}`)
    .join("\n");

  const projectsText = projects
    .map((p) => `- ${p.title}: ${p.description} (Tecnologías: ${p.tags.join(", ")}). Demo: ${p.demo}`)
    .join("\n");

  // ... similarly for experiences, educations, socialLinks

  return `
 Eres el asistente personal de Roger Civ, desarrollador web full stack.
 ...

 HABILIDADES TÉCNICAS
${skillsText}

 EXPERIENCIA
${experienceText}
 ...
  `;
}
The returned string serves as the system field in the Groq API call. It includes:
SectionSource
Personal bio and valuesHardcoded in the template literal
Technical skillsdata/skills.ts
Work experiencedata/experience.ts (experiences)
Educationdata/experience.ts (educations)
Projectsdata/projects.ts
Social linksdata/socials.ts
Assistant behavior rulesHardcoded in the template literal
The API route caches the output of this function for 5 minutes to avoid regenerating the string on every request:
let cachedContext: string | null = null;
let lastContextUpdate = 0;
const CONTEXT_CACHE_DURATION = 5 * 60 * 1000;

function getPortfolioContext() {
  const now = Date.now();
  if (!cachedContext || now - lastContextUpdate > CONTEXT_CACHE_DURATION) {
    cachedContext = generatePortfolioContext();
    lastContextUpdate = now;
  }
  return cachedContext;
}

Data-to-Component Map

Data fileExported symbolConsumed by
experience.tseducations: TimelineItem[]Experience.tsxTimelineSection.tsx
experience.tsexperiences: TimelineItem[]Experience.tsxTimelineSection.tsx
projects.tsprojects: Project[]Works.tsx
skills.tsskills: Skill[]Skills.tsx
socials.tssocialLinks: SocialLink[]portfolio-context.ts
portfolio-context.tsgeneratePortfolioContext()app/api/chat/route.ts

Project Structure Overview

Directory tree, App Router setup, and data flow diagram.

Components

Props and implementation details for every React component.

Build docs developers (and LLMs) love