Skip to main content
This page documents all content collections defined in src/content.config.ts. The portfolio uses Astro’s Content Collections API with Zod schemas for type-safe content management.

Overview

The content configuration defines 11 collections with strict type validation using Zod schemas. Collections are split between data loaders (JSON files) and content-type collections (MDX files).
// Content configuration location
~/workspace/source/src/content.config.ts

Collection Types

Collections that use the file() loader to load data from JSON files:
  • socials - Social media links
  • books - Book recommendations
  • techStack - Technology stack items
  • desktopSetup - Desktop setup gear

Data Collections

Socials Collection

Social media profiles and external links.
loader: file('src/data/socials.json')
id
string
required
Unique identifier for the social platformExample: "twitter", "github", "linkedin"
name
string
required
Display name of the social platformExample: "Twitter", "GitHub", "LinkedIn"
url
string (URL)
required
Full URL to the social profile. Must be a valid URL format.Example: "https://twitter.com/lewiskori"Validation: z.string().url()
icon
string
required
Icon identifier for displaying the platform iconExample: "twitter", "github-alt", "linkedin-in"
ariaLabel
string
required
Accessibility label for screen readersExample: "Follow me on Twitter", "View my GitHub profile"

Books Collection

Recommended books and reading list.
loader: file('src/data/books.json')
id
string
required
Unique identifier for the bookExample: "atomic-habits", "zero-to-one"
title
string
required
Full title of the bookExample: "Atomic Habits", "Zero to One"
author
string
required
Author name(s)Example: "James Clear", "Peter Thiel"
image
string
required
Path or URL to the book cover imageExample: "/images/books/atomic-habits.jpg"
description
string
required
Brief description or review of the bookExample: "Practical strategies for building good habits and breaking bad ones"
Link to purchase or learn more about the bookExample: "https://amzn.to/..."

Tech Stack Collection

Technologies and tools used in development.
loader: file('src/data/tech-stack.json')
id
string
required
Unique identifier for the technologyExample: "astro", "typescript", "tailwind"
name
string
required
Display name of the technologyExample: "Astro", "TypeScript", "Tailwind CSS"
image
string
required
Path or URL to the technology logoExample: "/images/tech/astro.svg"
description
string
required
Brief description of how the technology is usedExample: "Static site generation framework"
Official website or documentation URLExample: "https://astro.build"Validation: z.string().url()

Desktop Setup Collection

Physical desk setup and equipment.
loader: file('src/data/desktop-setup.json')
id
string
required
Unique identifier for the itemExample: "monitor", "keyboard", "desk"
name
string
required
Product nameExample: "Dell UltraSharp 27\"", "Keychron K2"
image
string
required
Path or URL to product imageExample: "/images/setup/monitor.jpg"
description
string
required
Description of the product and why it’s usedExample: "4K monitor for development work"
Purchase link or product pageExample: "https://amzn.to/..."

Content Collections

About Collection

About page content without schema validation.
type: 'content'
schema
none
No schema defined - allows flexible frontmatterContent Type: MDX/Markdown files in src/content/about/

Operating Notes Collection

Personal operating principles and work philosophy.
type: 'content'
title
string
required
Title of the operating noteExample: "How I Work", "Communication Guidelines"
subtitle
string
required
Subtitle or tagline for the noteExample: "Principles for effective collaboration"
description
string
required
Brief description for SEO and previewsExample: "My approach to async communication and deep work"
lastUpdated
Date
required
Last modification dateFormat: ISO date string (coerced to Date object)Example: "2024-03-15"Date objectValidation: z.coerce.date()

Advisory Collection

Advisory services and consulting information.
type: 'content'
title
string
required
Title of the advisory service or pageExample: "Technical Advisory", "Product Strategy"
subtitle
string
required
Subtitle describing the serviceExample: "Help scaling your technical organization"
description
string
required
Detailed description for SEOExample: "Strategic technical advice for growing companies"
lastUpdated
Date
required
Last modification dateValidation: z.coerce.date()
Optional featured image for the advisory pageExample: "/images/advisory/hero.jpg"

Blog Collection

Blog posts and articles.
type: 'content'
title
string
required
Blog post titleExample: "Building Scalable Systems"
author
string
required
Author nameExample: "Lewis Kori"
tags
string[]
required
Array of tag strings for categorizationExample: ["python", "web-development", "api"]Validation: z.array(z.string())
description
string
required
Post summary for SEO and previewsExample: "Learn how to build scalable backend systems"
dateCreated
Date
required
Publication dateValidation: z.coerce.date()
cover_image
string
Optional cover image pathExample: "/images/blog/post-cover.jpg"
series
string
Optional series name if post is part of a seriesExample: "Django REST Framework Tutorial"
sponsors
string[]
Optional array of sponsor IDs for the postExample: ["sponsor-1", "sponsor-2"]Validation: z.array(z.string())
canonical_url
string (URL)
Optional canonical URL if post was published elsewhere firstExample: "https://dev.to/lewiskori/..."Validation: z.string().url()

Experience Collection

Professional work experience entries.
type: 'content'
company
string
required
Company or organization nameExample: "Acme Corp", "Self-employed"
website
string (URL)
required
Company website URLExample: "https://acmecorp.com"Validation: z.string().url()
role
string
required
Job title or roleExample: "Senior Software Engineer", "CTO"
period
string
required
Employment period as a stringExample: "Jan 2020 - Present", "2018-2020"
order
number
required
Numeric order for sorting (lower numbers first)Example: 1, 2, 3Usage: Used to control display order of experience entries

Projects Collection

Portfolio projects showcase.
type: 'content'
title
string
required
Project nameExample: "E-commerce Platform", "API Gateway"
tech
string[]
required
Array of technologies usedExample: ["React", "Node.js", "PostgreSQL"]Validation: z.array(z.string())
Optional live project URLExample: "https://project.com"Validation: z.string().url()
Optional GitHub repository URLExample: "https://github.com/user/repo"Validation: z.string().url()
app_store
string (URL)
Optional iOS App Store URLExample: "https://apps.apple.com/..."Validation: z.string().url()
google_play
string (URL)
Optional Google Play Store URLExample: "https://play.google.com/store/apps/..."Validation: z.string().url()
cover_image
string
Optional project cover imageExample: "/images/projects/project-cover.jpg"
Whether to feature this project prominentlyExample: true, falseDefault: false (if not specified)
year
number
required
Year the project was completed or launchedExample: 2024, 2023
made_at
string
Optional company or context where project was madeExample: "Acme Corp", "Personal Project"

Sponsors Collection

Site sponsors and supporters.
type: 'content'
name
string
required
Sponsor name or companyExample: "Acme Corp", "John Doe"
url
string (URL)
required
Sponsor website URLExample: "https://sponsor.com"Validation: z.string().url()
twitter
string (URL)
Optional Twitter/X profile URLExample: "https://twitter.com/sponsor"Validation: z.string().url()
Optional sponsor logo pathExample: "/images/sponsors/logo.svg"
excerpt
string
required
Brief description of the sponsorExample: "Building the future of developer tools"
Whether to feature this sponsor prominentlyExample: true, false

Complete Schema Definition

import { file } from 'astro/loaders';
import { defineCollection, z } from 'astro:content';

export const collections = {
  socials: defineCollection({
    loader: file('src/data/socials.json'),
    schema: z.object({
      id: z.string(),
      name: z.string(),
      url: z.string().url(),
      icon: z.string(),
      ariaLabel: z.string(),
    }),
  }),
  
  books: defineCollection({
    loader: file('src/data/books.json'),
    schema: z.object({
      id: z.string(),
      title: z.string(),
      author: z.string(),
      image: z.string(),
      description: z.string(),
      link: z.string(),
    }),
  }),
  
  techStack: defineCollection({
    loader: file('src/data/tech-stack.json'),
    schema: z.object({
      id: z.string(),
      name: z.string(),
      image: z.string(),
      description: z.string(),
      link: z.string().url(),
    }),
  }),
  
  desktopSetup: defineCollection({
    loader: file('src/data/desktop-setup.json'),
    schema: z.object({
      id: z.string(),
      name: z.string(),
      image: z.string(),
      description: z.string(),
      link: z.string(),
    }),
  }),
  
  about: defineCollection({
    type: 'content',
  }),
  
  operatingNotes: defineCollection({
    type: 'content',
    schema: z.object({
      title: z.string(),
      subtitle: z.string(),
      description: z.string(),
      lastUpdated: z.coerce.date(),
    }),
  }),
  
  advisory: defineCollection({
    type: 'content',
    schema: z.object({
      title: z.string(),
      subtitle: z.string(),
      description: z.string(),
      lastUpdated: z.coerce.date(),
      featuredImage: z.string().optional(),
    }),
  }),
  
  blog: defineCollection({
    type: 'content',
    schema: z.object({
      title: z.string(),
      author: z.string(),
      tags: z.array(z.string()),
      description: z.string(),
      dateCreated: z.coerce.date(),
      cover_image: z.string().optional(),
      series: z.string().optional(),
      sponsors: z.array(z.string()).optional(),
      canonical_url: z.string().url().optional(),
    }),
  }),
  
  experience: defineCollection({
    type: 'content',
    schema: z.object({
      company: z.string(),
      website: z.string().url(),
      role: z.string(),
      period: z.string(),
      order: z.number(),
    }),
  }),
  
  projects: defineCollection({
    type: 'content',
    schema: z.object({
      title: z.string(),
      tech: z.array(z.string()),
      external_link: z.string().url().optional(),
      github_link: z.string().url().optional(),
      app_store: z.string().url().optional(),
      google_play: z.string().url().optional(),
      cover_image: z.string().optional(),
      featured: z.boolean().optional(),
      year: z.number(),
      made_at: z.string().optional(),
    }),
  }),
  
  sponsors: defineCollection({
    type: 'content',
    schema: z.object({
      name: z.string(),
      url: z.string().url(),
      twitter: z.string().url().optional(),
      logo: z.string().optional(),
      excerpt: z.string(),
      is_featured: z.boolean(),
    }),
  }),
};

Usage Example

Query content collections in your Astro pages:
---
import { getCollection } from 'astro:content';

// Get all blog posts
const posts = await getCollection('blog');

// Get featured projects
const projects = await getCollection('projects', ({ data }) => {
  return data.featured === true;
});

// Get socials
const socials = await getCollection('socials');
---

<ul>
  {posts.map(post => (
    <li>{post.data.title}</li>
  ))}
</ul>

Type Safety

All schemas provide full TypeScript type safety:
import type { CollectionEntry } from 'astro:content';

// Typed blog post
type BlogPost = CollectionEntry<'blog'>;

// Access with autocomplete
const post: BlogPost = await getEntry('blog', 'my-post');
console.log(post.data.title); // ✓ Type-safe
console.log(post.data.invalid); // ✗ TypeScript error

Build docs developers (and LLMs) love