Getting Started
Creating a custom template for GitFolio involves building a React component that receives standardized user data and renders a portfolio website. This guide walks you through the entire process.
Prerequisites
Before creating a template, ensure you have:
Node.js (version 20 or higher)
pnpm (version 10.4.1 or higher)
TypeScript knowledge
React fundamentals
Tailwind CSS familiarity
Development Setup
1. Clone and Install
git clone https://github.com/Skb3611/GitFolio.git
cd GitFolio
pnpm install
2. Build the Renderer
pnpm run build --filter=renderer
3. Start Development Server
pnpm run dev --filter=renderer
The renderer package at packages/renderer provides live preview of templates during development.
Template Structure
Directory Layout
Create your template directory in packages/templates/src/Templates/:
packages/templates/src/Templates/
└── MyAwesomeTemplate/
├── components/
│ ├── Hero.tsx
│ ├── Projects.tsx
│ ├── Experience.tsx
│ ├── Skills.tsx
│ └── Footer.tsx
└── template.tsx
Main Template File
Create template.tsx as your template’s entry point:
packages/templates/src/Templates/MyAwesomeTemplate/template.tsx
"use client" ;
import React from "react" ;
import { DummyData } from "../dummyData" ;
import { DATA } from "@workspace/types" ;
import { useTheme } from "next-themes" ;
const template = ({ data = DummyData } : { data ?: DATA }) => {
const { setTheme } = useTheme ();
React . useEffect (() => {
setTheme ( "dark" ); // Set your preferred default theme
}, []);
return (
< div className = "min-h-screen bg-background" >
{ /* Your template content */ }
< h1 > { data . personalInfo . full_name } </ h1 >
</ div >
);
};
export default template ;
Why use 'use client' directive?
GitFolio uses Next.js, and templates often need client-side features like hooks, event handlers, and theme switching. The "use client" directive marks this component as a Client Component.
Understanding the DATA Interface
Your template receives a DATA object with all user information:
import { DATA } from "@workspace/types" ;
interface DATA {
personalInfo : PersonalInformation ;
projects : Projects [];
experience : Experience [];
education : Education [];
socialLinks : SocialLinks ;
skills : string [];
}
interface PersonalInformation {
profileImg : string ;
full_name : string ;
username : string ;
email : string ;
location : string | null ;
tagline : string | null ;
bio : string | null ;
website : string | null ;
githubLink : string ;
followers : number ;
following : number ;
activeTemplateId ?: string ;
}
Usage example:
< img src = { data . personalInfo . profileImg } alt = { data . personalInfo . full_name } />
< h1 > { data . personalInfo . full_name } </ h1 >
< p > { data . personalInfo . tagline } </ p >
Projects
interface Projects {
id : string ;
name : string ;
description : string ;
thumbnail : string ;
repoLink : string ;
topics : string [];
liveLink : string ;
languages : Object ;
stars : number ;
forks : number ;
isIncluded : boolean ;
}
Usage example:
{ data . projects
. filter ( p => p . isIncluded )
. map ( project => (
< div key = { project . id } >
< h3 > { project . name } </ h3 >
< p > { project . description } </ p >
< a href = { project . liveLink } > View Live </ a >
</ div >
))}
Experience
interface Experience {
id : string ;
logo : string ;
company : string ;
role : string ;
description : string ;
start_date : string ;
end_date : string ;
onGoing : boolean ;
}
Education
interface Education {
id : string ;
logo : string ;
title : string ;
institution : string ;
description : string ;
start_date : string ;
end_date : string ;
onGoing : boolean ;
}
Social Links
interface SocialLinks {
github : string | null ;
linkedin : string | null ;
twitter : string | null ;
website : string | null ;
instagram : string | null ;
facebook : string | null ;
behance : string | null ;
youtube : string | null ;
}
Building Components
Hero Component Example
packages/templates/src/Templates/MyAwesomeTemplate/components/Hero.tsx
import { PersonalInformation } from "@workspace/types" ;
interface HeroProps {
data : PersonalInformation ;
}
export default function Hero ({ data } : HeroProps ) {
return (
< section className = "min-h-screen flex items-center justify-center" >
< div className = "text-center space-y-4" >
< img
src = { data . profileImg }
alt = { data . full_name }
className = "w-32 h-32 rounded-full mx-auto"
/>
< h1 className = "text-5xl font-bold" > { data . full_name } </ h1 >
{ data . tagline && (
< p className = "text-xl text-muted-foreground" > { data . tagline } </ p >
) }
{ data . bio && < p className = "max-w-2xl mx-auto" > { data . bio } </ p > }
</ div >
</ section >
);
}
Projects Section Example
packages/templates/src/Templates/MyAwesomeTemplate/components/Projects.tsx
import { Projects } from "@workspace/types" ;
interface ProjectsSectionProps {
data : Projects [];
}
export default function ProjectsSection ({ data } : ProjectsSectionProps ) {
const includedProjects = data . filter ( p => p . isIncluded );
if ( includedProjects . length === 0 ) return null ;
return (
< section className = "py-20" >
< h2 className = "text-3xl font-bold mb-8" > Projects </ h2 >
< div className = "grid md:grid-cols-2 lg:grid-cols-3 gap-6" >
{ includedProjects . map ( project => (
< div key = { project . id } className = "border rounded-lg p-6" >
< img
src = { project . thumbnail }
alt = { project . name }
className = "w-full h-48 object-cover rounded mb-4"
/>
< h3 className = "text-xl font-semibold mb-2" > { project . name } </ h3 >
< p className = "text-muted-foreground mb-4" >
{ project . description }
</ p >
< div className = "flex gap-2 flex-wrap mb-4" >
{ project . topics . map ( topic => (
< span
key = { topic }
className = "px-2 py-1 bg-secondary rounded text-sm"
>
{ topic }
</ span >
)) }
</ div >
< div className = "flex gap-4" >
< a href = { project . liveLink } className = "text-blue-500" >
Live Demo
</ a >
< a href = { project . repoLink } className = "text-blue-500" >
View Code
</ a >
</ div >
</ div >
)) }
</ div >
</ section >
);
}
Styling with Tailwind
GitFolio uses Tailwind CSS for styling. Use Tailwind’s utility classes:
< div className = "max-w-6xl mx-auto px-4 py-20" >
< h1 className = "text-4xl font-bold text-foreground" >
Title
</ h1 >
< p className = "text-muted-foreground mt-4" >
Description
</ p >
</ div >
Theme-aware Styling
// Light mode: white background, dark text
// Dark mode: dark background, light text
< div className = "bg-background text-foreground" >
// Conditional styling based on theme
< div className = "bg-white dark:bg-gray-900 text-black dark:text-white" >
Use Tailwind’s semantic color classes like bg-background, text-foreground, and text-muted-foreground for automatic theme switching.
Theme Integration
Setting Default Theme
import { useTheme } from "next-themes" ;
const template = ({ data = DummyData } : { data ?: DATA }) => {
const { setTheme } = useTheme ();
React . useEffect (() => {
setTheme ( "dark" ); // or "light"
}, []);
return < div > ... </ div > ;
};
Adding Theme Toggle
import { useTheme } from "next-themes" ;
import { Moon , Sun } from "lucide-react" ;
function ThemeToggle () {
const { theme , setTheme } = useTheme ();
return (
< button
onClick = { () => setTheme ( theme === "dark" ? "light" : "dark" ) }
className = "p-2 rounded-lg hover:bg-secondary"
>
{ theme === "dark" ? < Sun /> : < Moon /> }
</ button >
);
}
Using Shared Components
Leverage GitFolio’s shared components from @workspace/ui:
import { Button } from "@workspace/ui/components/button" ;
import { Card } from "@workspace/ui/components/card" ;
import { Avatar , AvatarImage } from "@workspace/ui/components/avatar" ;
// Use in your template
< Button variant = "default" size = "lg" >
Contact Me
</ Button >
< Card className = "p-6" >
< Avatar >
< AvatarImage src = { data . personalInfo . profileImg } />
</ Avatar >
</ Card >
Adding Animations
Use Motion (Framer Motion) for animations:
import { motion } from "motion/react" ;
< motion.div
initial = { { opacity: 0 , y: 20 } }
animate = { { opacity: 1 , y: 0 } }
transition = { { duration: 0.5 } }
>
< h1 > Animated Content </ h1 >
</ motion.div >
Exporting Your Template
1. Export from Template Directory
Add to packages/templates/src/Templates/index.ts:
export { default as MyAwesomeTemplate } from "./MyAwesomeTemplate/template" ;
2. Test in Renderer
Update packages/renderer/app/page.tsx:
import { MyAwesomeTemplate } from "@workspace/templates" ;
export default function Page () {
return < MyAwesomeTemplate /> ;
}
3. Run Preview
pnpm run dev --filter=renderer
Visit http://localhost:3000 to see your template.
Best Practices
Always handle missing data
Use optional chaining and provide fallbacks: { data . personalInfo . tagline ?? "Add a tagline" }
{ data . projects . length > 0 && < Projects data = { data . projects } /> }
Each component should have a single responsibility. Don’t create monolithic components.
Test on mobile, tablet, and desktop. Use Tailwind’s responsive classes: < div className = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4" >
Use proper HTML5 elements for accessibility: < header > , < nav > , < main > , < section > , < article > , < footer >
Always add alt text and use appropriate image sizing: < img src = { src } alt = "Descriptive text" className = "w-full h-auto" />
Testing Your Template
Build Test
Ensure your template builds without errors:
pnpm run build --filter=renderer
Responsive Testing
Test your template at these breakpoints:
Mobile: 375px, 428px
Tablet: 768px, 1024px
Desktop: 1440px, 1920px
Theme Testing
Verify both light and dark themes:
// In your browser console
localStorage . setItem ( 'theme' , 'dark' )
localStorage . setItem ( 'theme' , 'light' )
Common Patterns
Conditional Rendering
// Only render section if data exists
{ data . experience . length > 0 && (
< ExperienceSection data = { data . experience } />
)}
// Provide fallback
{ data . personalInfo . bio || "No bio available" }
import { format } from "date-fns" ;
const formattedDate = format ( new Date ( experience . start_date ), "MMM yyyy" );
Filtering Projects
const includedProjects = data . projects . filter ( p => p . isIncluded );
Next Steps
Contribute Your Template Submit your template to GitFolio
Template Architecture Deep dive into template architecture