Data Fetching Functions API¶
Navigation: Documentation Home > Content Management > Data Fetching
Table of Contents¶
- Introduction
- Project Functions
- Achievement Functions
- Skill Functions
- Function Implementation Details
- Error Handling
- Performance Considerations
- Caching Strategy
- Usage in Components
- Usage in Pages
- Advanced Patterns
- See Also
- Next Steps
Introduction¶
Data fetching functions provide a clean API for accessing content from JSON files. These functions handle file reading, parsing, validation, and sorting, returning type-safe objects ready for use in components and pages.
Core Principles¶
- Async/Await: All functions are async for consistency
- Type Safety: Return typed objects, not
any - Error Handling: Graceful fallbacks for invalid data
- Sorting: Consistent, predictable ordering
- Simplicity: Easy to use, hard to misuse
Available Functions¶
// Projects (lib/projects.ts)
getProjects(): Promise<Project[]>
getFeaturedProjects(): Promise<Project[]>
getProjectBySlug(slug: string): Promise<Project | null>
// Achievements (lib/achievements.ts)
getAchievements(): Promise<Achievement[]>
// Skills (lib/skills.ts)
getSkills(): Promise<Skill[]>
generateSkillId(skillName: string): string
Project Functions¶
getProjects()¶
Reads all project JSON files and returns sorted array.
Signature¶
Returns¶
Promise<Project[]> - Array of all projects, sorted by:
- Projects with
orderfield (ascending) - Projects without
orderfield (alphabetical by title)
Usage¶
import { getProjects } from "@/lib/projects";
// In Server Component
export default async function ProjectsPage() {
const projects = await getProjects();
return (
<div>
<h1>All Projects ({projects.length})</h1>
{projects.map(project => (
<ProjectCard key={project.slug} project={project} />
))}
</div>
);
}
// In Route Handler
import { NextResponse } from "next/server";
import { getProjects } from "@/lib/projects";
export async function GET() {
const projects = await getProjects();
return NextResponse.json(projects);
}
Sorting Behavior¶
// Example projects:
const projects = [
{ slug: "z-project", title: "Z Project", order: 1 }, // Position 1
{ slug: "a-project", title: "A Project", order: 2 }, // Position 2
{ slug: "m-project", title: "M Project", order: 10 }, // Position 3
{ slug: "b-project", title: "B Project" }, // Position 4 (alphabetical)
{ slug: "c-project", title: "C Project" }, // Position 5 (alphabetical)
{ slug: "d-project", title: "D Project" }, // Position 6 (alphabetical)
];
Error Handling¶
// Invalid JSON file - skipped with console error
// File: content/projects/broken.json
{
"title": "Test",
"invalid": json
}
// Console: Error parsing JSON for project broken
// Invalid structure - skipped with console error
// File: content/projects/array.json
[
{"title": "Test"}
]
// Console: Invalid JSON structure for project array
getFeaturedProjects()¶
Returns only featured projects (order 1-6) for homepage.
Signature¶
Returns¶
Promise<Project[]> - Array of featured projects (order 1-6), sorted by order.
Usage¶
import { getFeaturedProjects } from "@/lib/projects";
// In Home Page
export default async function HomePage() {
const featured = await getFeaturedProjects();
return (
<section>
<h2>Featured Projects</h2>
<div className="grid grid-cols-3 gap-4">
{featured.map(project => (
<FeaturedProjectCard key={project.slug} project={project} />
))}
</div>
</section>
);
}
Implementation Logic¶
export async function getFeaturedProjects(): Promise<Project[]> {
const projects = await getProjects();
return projects.filter((project) => {
// Include projects with order 1-6
return project.order !== undefined && project.order >= 1 && project.order <= 6;
});
}
Examples¶
// Projects
const allProjects = [
{ slug: "p1", title: "P1", order: 1 }, // ✅ Featured
{ slug: "p2", title: "P2", order: 2 }, // ✅ Featured
{ slug: "p3", title: "P3", order: 6 }, // ✅ Featured
{ slug: "p4", title: "P4", order: 7 }, // ❌ Not featured (order > 6)
{ slug: "p5", title: "P5" }, // ❌ Not featured (no order)
];
const featured = await getFeaturedProjects();
// Result: [p1, p2, p3]
getProjectBySlug()¶
Retrieves a single project by its slug.
Signature¶
Parameters¶
slug(string): The project slug (filename without .json)
Returns¶
Promise<Project | null>- The project if found,nullotherwise
Usage¶
import { getProjectBySlug } from "@/lib/projects";
import { notFound } from "next/navigation";
// In Dynamic Route: app/projects/[slug]/page.tsx
export default async function ProjectPage({
params
}: {
params: { slug: string }
}) {
const project = await getProjectBySlug(params.slug);
if (!project) {
notFound(); // Shows 404 page
}
return (
<div>
<h1>{project.title}</h1>
<p>{project.description}</p>
{/* ... */}
</div>
);
}
// Generate static params for all projects
export async function generateStaticParams() {
const projects = await getProjects();
return projects.map((project) => ({
slug: project.slug,
}));
}
Null Handling¶
// ✅ Good - check for null
const project = await getProjectBySlug("unknown-project");
if (project === null) {
console.log("Project not found");
} else {
console.log(project.title);
}
// ✅ Good - use optional chaining
const title = (await getProjectBySlug("unknown"))?.title ?? "Unknown";
// ❌ Bad - no null check (runtime error)
const project = await getProjectBySlug("unknown-project");
console.log(project.title); // Error: Cannot read property 'title' of null
Achievement Functions¶
getAchievements()¶
Reads all achievement JSON files and returns sorted array.
Signature¶
Returns¶
Promise<Achievement[]> - Array of all achievements, sorted by:
- Achievements with
orderfield (ascending) - Achievements without
orderfield (by date, newest first)
Usage¶
import { getAchievements } from "@/lib/achievements";
// In Server Component
export default async function HomePage() {
const achievements = await getAchievements();
// Get first 3 for homepage
const topAchievements = achievements.slice(0, 3);
return (
<section>
<h2>Recent Achievements</h2>
{topAchievements.map(achievement => (
<AchievementCard key={achievement.slug} achievement={achievement} />
))}
</section>
);
}
// Filter by type
export default async function CertificationsPage() {
const achievements = await getAchievements();
const certifications = achievements.filter(a => a.type === "certification");
return (
<div>
<h1>Certifications</h1>
{certifications.map(cert => (
<CertificationCard key={cert.slug} certification={cert} />
))}
</div>
);
}
Sorting Behavior¶
// Example achievements:
const achievements = [
{ slug: "a1", title: "A1", order: 1, date: "2020" }, // Position 1 (order)
{ slug: "a2", title: "A2", order: 2, date: "2020" }, // Position 2 (order)
{ slug: "a3", title: "A3", date: "2024-06" }, // Position 3 (newest date)
{ slug: "a4", title: "A4", date: "2023-12" }, // Position 4
{ slug: "a5", title: "A5", date: "2023-01" }, // Position 5
{ slug: "a6", title: "A6", date: "2022" }, // Position 6 (oldest date)
];
Filtering by Type¶
import { getAchievements, Achievement } from "@/lib/achievements";
async function getCertifications(): Promise<Achievement[]> {
const all = await getAchievements();
return all.filter((a) => a.type === "certification");
}
async function getAwards(): Promise<Achievement[]> {
const all = await getAchievements();
return all.filter((a) => a.type === "award");
}
async function getGeneralAchievements(): Promise<Achievement[]> {
const all = await getAchievements();
return all.filter((a) => a.type === "achievement");
}
Skill Functions¶
getSkills()¶
Generates skills from all project technologies.
Signature¶
Returns¶
Promise<Skill[]> - Array of skills with associated projects, sorted alphabetically by name.
Usage¶
import { getSkills } from "@/lib/skills";
// In Skills Page
export default async function SkillsPage() {
const skills = await getSkills();
return (
<div>
<h1>Skills ({skills.length})</h1>
{skills.map(skill => (
<div key={skill.id}>
<h2>{skill.name}</h2>
<p>{skill.projects.length} projects</p>
<ul>
{skill.projects.map(project => (
<li key={project.slug}>{project.title}</li>
))}
</ul>
</div>
))}
</div>
);
}
// Get skill by ID
const skills = await getSkills();
const reactSkill = skills.find(s => s.id === "react");
// Get most used skills
const topSkills = skills
.sort((a, b) => b.projects.length - a.projects.length)
.slice(0, 10);
Generation Algorithm¶
flowchart TD
A[Start] --> B[Get All Projects]
B --> C[Create Empty Skills Map]
C --> D{For Each Project}
D --> E{For Each Technology}
E --> F[Generate Skill ID]
F --> G{Skill Exists?}
G -->|Yes| H[Add Project to Skill]
G -->|No| I[Create New Skill]
I --> J[Add to Map]
H --> K{More Technologies?}
J --> K
K -->|Yes| E
K -->|No| L{More Projects?}
L -->|Yes| D
L -->|No| M[Convert Map to Array]
M --> N[Sort by Name]
N --> O[Return Skills]
style A fill:#e8f5e9
style O fill:#c8e6c9
style C fill:#fff9c4
style F fill:#ffe0b2
style G fill:#f3e5f5
Example Generation¶
// Input Projects
const projects = [
{
slug: "project-a",
title: "Project A",
technologies: ["React", "TypeScript", "Docker"],
},
{
slug: "project-b",
title: "Project B",
technologies: ["React", "Next.js", "PostgreSQL"],
},
{
slug: "project-c",
title: "Project C",
technologies: ["Python", "FastAPI", "Docker"],
},
];
// Generated Skills
const skills = [
{
id: "docker",
name: "Docker",
projects: [
/* Project A, Project C */
], // 2 projects
},
{
id: "fastapi",
name: "FastAPI",
projects: [
/* Project C */
], // 1 project
},
{
id: "nextjs",
name: "Next.js",
projects: [
/* Project B */
], // 1 project
},
{
id: "postgresql",
name: "PostgreSQL",
projects: [
/* Project B */
], // 1 project
},
{
id: "python",
name: "Python",
projects: [
/* Project C */
], // 1 project
},
{
id: "react",
name: "React",
projects: [
/* Project A, Project B */
], // 2 projects
},
{
id: "typescript",
name: "TypeScript",
projects: [
/* Project A */
], // 1 project
},
];
// Sorted alphabetically by name
generateSkillId()¶
Converts skill name to URL-safe ID.
Signature¶
Parameters¶
skillName(string): Original skill name (e.g., "React", "Next.js")
Returns¶
string - URL-safe skill ID (e.g., "react", "nextjs")
Usage¶
import { generateSkillId } from "@/lib/skills";
// Basic usage
const id = generateSkillId("React");
console.log(id); // "react"
// Complex names
generateSkillId("Next.js"); // "nextjs"
generateSkillId("Tailwind CSS"); // "tailwind-css"
generateSkillId("ASP.NET Core"); // "aspnet-core"
generateSkillId("Node.js (Express)"); // "nodejs-express"
generateSkillId("C++"); // "c"
generateSkillId("PostgreSQL"); // "postgresql"
// Use in URLs
const skillId = generateSkillId(skillName);
const url = `/skills/${skillId}`;
Transformation Rules¶
// Step-by-step transformation
const input = "Next.js (Framework)";
// 1. Lowercase
("next.js (framework)");
// 2. Remove special characters (except spaces and hyphens)
("nextjs framework");
// 3. Replace spaces with hyphens
("nextjs-framework");
// 4. Replace multiple hyphens with single hyphen
("nextjs-framework");
// 5. Remove leading/trailing hyphens
("nextjs-framework"); // Final result
Function Implementation Details¶
File Reading Pattern¶
import fs from "fs";
import path from "path";
// 1. Define directory path
const projectsDirectory = path.join(process.cwd(), "content/projects");
// 2. Read all filenames
const fileNames = fs.readdirSync(projectsDirectory);
// 3. Filter .json files
const jsonFiles = fileNames.filter((fileName) => fileName.endsWith(".json"));
// 4. Read and parse each file
const projects = jsonFiles.map((fileName) => {
const slug = fileName.replace(/\.json$/, "");
const filePath = path.join(projectsDirectory, fileName);
const fileContent = fs.readFileSync(filePath, "utf8");
const projectData = JSON.parse(fileContent);
return {
slug,
...projectData,
};
});
Error Handling Pattern¶
// Safe JSON parsing with error handling
let projectData: unknown;
try {
projectData = JSON.parse(fileContent);
} catch (error) {
console.error(`Error parsing JSON for project ${slug}`, { error, fileName });
return null; // Skip invalid files
}
// Validate data structure
if (typeof projectData !== "object" || projectData === null || Array.isArray(projectData)) {
console.error(`Invalid JSON structure for project ${slug}`, { projectData });
return null; // Skip invalid structure
}
// Filter out null values
const projects = allData.filter((project): project is Project => project !== null);
Sorting Implementation¶
// Projects sorting
projects.sort((a, b) => {
// If both have order, sort by order
if (a.order !== undefined && b.order !== undefined) {
return a.order - b.order;
}
// If only one has order, it comes first
if (a.order !== undefined && b.order === undefined) return -1;
if (a.order === undefined && b.order !== undefined) return 1;
// If neither has order, sort alphabetically
return a.title.localeCompare(b.title);
});
// Achievements sorting
achievements.sort((a, b) => {
// If both have order, sort by order
if (a.order !== undefined && b.order !== undefined) {
return a.order - b.order;
}
// If only one has order, it comes first
if (a.order !== undefined && b.order === undefined) return -1;
if (a.order === undefined && b.order !== undefined) return 1;
// If neither has order, sort by date (newest first)
const dateA = new Date(a.date).getTime();
const dateB = new Date(b.date).getTime();
return dateB - dateA;
});
// Skills sorting
skills.sort((a, b) => a.name.localeCompare(b.name));
Error Handling¶
Graceful Degradation¶
// Missing directory
export async function getAchievements(): Promise<Achievement[]> {
// Check if directory exists
if (!fs.existsSync(achievementsDirectory)) {
return []; // Return empty array instead of throwing
}
// ... rest of function
}
// Invalid JSON
try {
projectData = JSON.parse(fileContent);
} catch (error) {
console.error(`Error parsing JSON for project ${slug}`, { error });
return null; // Skip this file, continue with others
}
// Invalid structure
if (typeof projectData !== "object" || projectData === null) {
console.error(`Invalid JSON structure for project ${slug}`);
return null; // Skip this file
}
Error Logging¶
// Console errors for debugging
console.error(`Error parsing JSON for project ${slug}`, {
error,
fileName,
filePath,
});
// In production, these should be logged to monitoring service
// Example with error tracking:
if (process.env.NODE_ENV === "production") {
errorTracker.captureException(error, {
context: {
slug,
fileName,
operation: "parse-project-json",
},
});
}
Performance Considerations¶
Build-Time vs Runtime¶
// ✅ At build time (Static Generation)
// Files read once, cached in static pages
export async function generateStaticParams() {
const projects = await getProjects(); // Read files once
return projects.map(p => ({ slug: p.slug }));
}
// ✅ At build time (Server Components)
export default async function ProjectsPage() {
const projects = await getProjects(); // Read files once per build
return <div>{/* ... */}</div>;
}
// ⚠️ At runtime (Route Handlers)
export async function GET() {
const projects = await getProjects(); // Read files on every request
return Response.json(projects);
}
// Consider caching for API routes
Caching Recommendations¶
// For API routes, implement caching
import { unstable_cache } from "next/cache";
const getCachedProjects = unstable_cache(async () => await getProjects(), ["projects"], {
revalidate: 3600, // Cache for 1 hour
tags: ["projects"],
});
export async function GET() {
const projects = await getCachedProjects();
return Response.json(projects);
}
Caching Strategy¶
Next.js Automatic Caching¶
// Server Components are cached automatically
export default async function ProjectsPage() {
// This data is cached during build
const projects = await getProjects();
return <div>{/* ... */}</div>;
}
// Force dynamic rendering
export const dynamic = "force-dynamic";
export default async function ProjectsPage() {
// This will fetch on every request
const projects = await getProjects();
return <div>{/* ... */}</div>;
}
Manual Cache Invalidation¶
import { revalidatePath, revalidateTag } from "next/cache";
// After updating content
export async function POST(request: Request) {
// ... update content ...
// Revalidate specific path
revalidatePath("/projects");
revalidatePath("/skills");
// Or revalidate by tag
revalidateTag("projects");
return Response.json({ success: true });
}
Usage in Components¶
Server Components¶
import { getProjects } from "@/lib/projects";
// ✅ Async Server Component
export default async function ProjectsList() {
const projects = await getProjects();
return (
<div>
{projects.map(project => (
<div key={project.slug}>
<h2>{project.title}</h2>
<p>{project.shortDescription}</p>
</div>
))}
</div>
);
}
Client Components¶
// ❌ Cannot use data fetching functions directly
"use client";
import { getProjects } from "@/lib/projects"; // Error!
export default function ProjectsList() {
const projects = await getProjects(); // Error! Can't use await in client component
// ...
}
// ✅ Pass data from Server Component
"use client";
import { Project } from "@/lib/projects";
interface Props {
projects: Project[];
}
export default function ProjectsList({ projects }: Props) {
return (
<div>
{projects.map(project => (
<div key={project.slug}>{project.title}</div>
))}
</div>
);
}
// ✅ Or fetch in parent Server Component
import { getProjects } from "@/lib/projects";
import ProjectsList from "@/components/projects-list";
export default async function ProjectsPage() {
const projects = await getProjects();
return <ProjectsList projects={projects} />;
}
Usage in Pages¶
Static Pages¶
// app/projects/page.tsx
import { getProjects } from "@/lib/projects";
export default async function ProjectsPage() {
const projects = await getProjects();
return (
<main>
<h1>Projects</h1>
<div className="grid">
{projects.map(project => (
<ProjectCard key={project.slug} project={project} />
))}
</div>
</main>
);
}
Dynamic Pages¶
// app/projects/[slug]/page.tsx
import { getProjects, getProjectBySlug } from "@/lib/projects";
import { notFound } from "next/navigation";
interface Props {
params: { slug: string };
}
// Generate static paths
export async function generateStaticParams() {
const projects = await getProjects();
return projects.map(project => ({
slug: project.slug,
}));
}
// Page component
export default async function ProjectPage({ params }: Props) {
const project = await getProjectBySlug(params.slug);
if (!project) {
notFound();
}
return (
<article>
<h1>{project.title}</h1>
<p>{project.description}</p>
{/* ... */}
</article>
);
}
// Generate metadata
export async function generateMetadata({ params }: Props) {
const project = await getProjectBySlug(params.slug);
if (!project) {
return { title: "Project Not Found" };
}
return {
title: project.title,
description: project.shortDescription,
};
}
Advanced Patterns¶
Parallel Data Fetching¶
// Fetch multiple data sources in parallel
export default async function HomePage() {
// ✅ Fetch in parallel
const [projects, achievements, skills] = await Promise.all([
getFeaturedProjects(),
getAchievements(),
getSkills(),
]);
return (
<main>
<FeaturedProjects projects={projects} />
<RecentAchievements achievements={achievements.slice(0, 3)} />
<TopSkills skills={skills.slice(0, 10)} />
</main>
);
}
Custom Filtering¶
import { getProjects, Project } from "@/lib/projects";
// Filter projects by technology
export async function getProjectsByTechnology(tech: string): Promise<Project[]> {
const projects = await getProjects();
return projects.filter((p) => p.technologies.includes(tech));
}
// Get projects with demos
export async function getProjectsWithDemos(): Promise<Project[]> {
const projects = await getProjects();
return projects.filter((p) => p.demoUrl !== undefined);
}
// Get projects by year (from order as proxy)
export async function getRecentProjects(count: number): Promise<Project[]> {
const projects = await getProjects();
return projects.slice(0, count);
}
Related Projects¶
import { getProjects, Project } from "@/lib/projects";
export async function getRelatedProjects(project: Project, limit: number = 3): Promise<Project[]> {
const allProjects = await getProjects();
// Find projects with shared technologies
const related = allProjects
.filter((p) => p.slug !== project.slug) // Exclude current project
.map((p) => ({
project: p,
sharedTech: p.technologies.filter((t) => project.technologies.includes(t)).length,
}))
.filter((item) => item.sharedTech > 0) // At least 1 shared tech
.sort((a, b) => b.sharedTech - a.sharedTech) // Most shared first
.slice(0, limit)
.map((item) => item.project);
return related;
}
See Also¶
- TypeScript Interfaces - Return types for these functions
- CMS Overview - System architecture
- Skills Generation - Detailed skills algorithm
- Adding Projects - Creating content these functions read
Next Steps¶
- Study the Code: Read actual implementations in
lib/directory - Practice Usage: Use these functions in your components
- Explore Patterns: Try custom filtering and sorting
- Build Features: Create new pages using these APIs
- Optimize: Implement caching for better performance
Last Updated: February 2026
Maintainer: Development Team
Related Files: lib/projects.ts, lib/achievements.ts, lib/skills.ts