# How to Manage Video Content with MDX and Supplementary Files in Next.js

> Learn to manage video content in Next.js using MDX and supplementary files. Discover a three-tier architecture for organizing videos efficiently, featuring MDX pages, metadata loading, and asset storage.

- Repository: [Ege Chelebi/blog](https://github.com/woosal1337/blog)
- Tags: how-to-guide
- Published: 2026-08-06

---

**The woosal1337/blog repository treats video episodes as first-class content types using a three-tier architecture: MDX pages for content and frontmatter, a dynamic metadata loader in [`lib/videos.ts`](https://github.com/woosal1337/blog/blob/main/lib/videos.ts), and a filesystem-based kit storage system for supplementary assets.**

Managing video content in a static blog requires structured metadata, dynamic loading, and organized asset delivery. The woosal1337/blog repository demonstrates a complete workflow to manage video content with MDX and supplementary files by combining Next.js dynamic imports with type-safe utilities and a dedicated storage pattern for companion resources.

## Architecture Overview

The system distributes video episode data across three distinct layers to separate content, metadata, and assets.

**MDX Content Layer.** Each episode lives in `app/(website)/videos/(episode)/<slug>/page.mdx` and exports a `meta` object conforming to `VideoEpisodeFrontmatter` defined in [`lib/video-utils.ts`](https://github.com/woosal1337/blog/blob/main/lib/video-utils.ts). This frontmatter includes the YouTube ID, title, publication date, chapters, and kit directory references.

**Metadata Loader.** The [`lib/videos.ts`](https://github.com/woosal1337/blog/blob/main/lib/videos.ts) module exports `getAllEpisodes`, `getEpisode`, and kit file utilities that scan episode directories and dynamically import MDX modules. This approach compiles content only when requested, keeping builds fast while maintaining type safety through `VideoEpisodeMeta`.

**Supplementary Kit Storage.** Additional assets such as code samples, notebooks, and transcripts reside in `videos/<slug>/kit/*`. These files are served through dedicated routes and accessed via helpers like `kitFileHref`, `listKitFiles`, and `readKitFile`.

## Defining Video Metadata Types

Type safety originates in [`lib/video-utils.ts`](https://github.com/woosal1337/blog/blob/main/lib/video-utils.ts), which exports the `VideoEpisodeFrontmatter` and `VideoEpisodeMeta` interfaces. These types enforce the exact shape of frontmatter exported from each MDX file.

The frontmatter structure includes:

- `title`: Episode title string
- `youtubeId`: YouTube video identifier
- `chapters`: Array of timecodes and titles for navigation
- `kit.dir`: Directory name for supplementary files
- `kit.repoUrl`: Source repository reference

When an MDX file exports its metadata, the loader processes it through `loadMeta()`, which performs a dynamic import: `import('@/app/(website)/videos/(episode)/${slug}/page.mdx')`.

## Loading Episodes Dynamically

Episode discovery happens through filesystem operations rather than hardcoded manifests. The `listSlugs()` function walks `app/(website)/videos/(episode)` and identifies every folder containing a `page.mdx` file.

The public API in [`lib/videos.ts`](https://github.com/woosal1337/blog/blob/main/lib/videos.ts) provides:

- **`getAllEpisodes()`**: Returns all visible episodes, filtering out drafts and hidden entries. Returns `Promise<VideoEpisodeMeta[]>`.
- **`getAllEpisodesWithHidden()`**: Includes episodes marked as hidden in the results.
- **`getEpisode(slug)`**: Loads a single episode by slug with full metadata.

This implementation enables automatic routing where adding a new folder with a `page.mdx` immediately makes the episode available without configuration changes.

## Managing Supplementary Kit Files

Video episodes often require accompanying code samples or documentation. The kit file system stores these in `videos/<slug>/kit/` and exposes them through utility functions.

**Path resolution** uses `kitFileHref(slug, file)` to generate URLs in the format `/videos/<slug>/kit/<file>`. **File discovery** uses `listKitFiles(dir)` to enumerate available assets, while `readKitFile(dir, file)` performs the actual filesystem read.

These utilities integrate with [`lib/video-utils.ts`](https://github.com/woosal1337/blog/blob/main/lib/video-utils.ts) helpers like `kitFileLanguage` to determine syntax highlighting based on file extensions.

## Rendering Video Components

The [`components/blocks/youtube-facade.tsx`](https://github.com/woosal1337/blog/blob/main/components/blocks/youtube-facade.tsx) component handles YouTube embedding using utilities from [`lib/video-utils.ts`](https://github.com/woosal1337/blog/blob/main/lib/video-utils.ts). It consumes the `youtubeId` from episode metadata and supports chapter navigation through `chapterUrl(youtubeId, startSeconds)` and `formatTimecode()`.

For episodes with chapter markers, the component generates deep links using `chapterUrl()`, which constructs YouTube URLs that start at specific timestamps. The `chapterSeconds()` helper converts timecode strings like "03:45" into seconds for the URL parameters.

### Example: Listing All Episodes

To render a video archive page, consume the metadata loader API:

```typescript
import { getAllEpisodes } from '@/lib/videos';

export default async function EpisodesPage() {
  const episodes = await getAllEpisodes(); // Returns VideoEpisodeMeta[]
  return (
    <ul>
      {episodes.map((e) => (
        <li key={e.slug}>
          <a href={`/videos/${e.slug}`}>{e.title}</a>
        </li>
      ))}
    </ul>
  );
}

```

### Example: Reading Kit Files

Access supplementary content within server components using the filesystem utilities:

```typescript
import { readKitFile } from '@/lib/videos';

export async function KitFileViewer({ 
  slug, 
  filename 
}: { 
  slug: string; 
  filename: string 
}) {
  const content = readKitFile(slug, `kit/${filename}`);
  return <pre className="language-python">{content}</pre>;
}

```

### Example: Embedding with Chapter Navigation

Combine the YouTube facade with chapter utilities for rich navigation:

```typescript
import { YoutubeFacade } from '@/components/blocks/youtube-facade';
import { chapterSeconds, chapterUrl, formatTimecode } from '@/lib/video-utils';

export function EpisodePlayer({ meta }: { meta: VideoEpisodeMeta }) {
  return (
    <>
      <YoutubeFacade youtubeId={meta.youtubeId} />
      {meta.chapters?.map((ch) => {
        const start = chapterSeconds(ch.time);
        return (
          <a href={chapterUrl(meta.youtubeId, start)} key={ch.time}>
            {formatTimecode(start)} – {ch.title}
          </a>
        );
      })}
    </>
  );
}

```

## Summary

- **Video episodes** in the woosal1337/blog repository are first-class content types stored as MDX files in `app/(website)/videos/(episode)/<slug>/page.mdx`.
- **Type safety** comes from `VideoEpisodeFrontmatter` and `VideoEpisodeMeta` interfaces defined in [`lib/video-utils.ts`](https://github.com/woosal1337/blog/blob/main/lib/video-utils.ts), enforced at build time when the loader imports MDX modules.
- **Dynamic loading** via [`lib/videos.ts`](https://github.com/woosal1337/blog/blob/main/lib/videos.ts) functions like `getAllEpisodes()` and `getEpisode()` uses Next.js dynamic imports to compile MDX on demand.
- **Kit files** (code samples, transcripts) reside in `videos/<slug>/kit/` and are accessed through `kitFileHref()`, `listKitFiles()`, and `readKitFile()`.
- **Rendering** utilizes [`components/blocks/youtube-facade.tsx`](https://github.com/woosal1337/blog/blob/main/components/blocks/youtube-facade.tsx) combined with URL helpers `watchUrl()`, `chapterUrl()`, and `formatTimecode()` for optimized video delivery with chapter support.

## Frequently Asked Questions

### How are video episode types defined in the codebase?

Type definitions reside in [`lib/video-utils.ts`](https://github.com/woosal1337/blog/blob/main/lib/video-utils.ts), which exports `VideoEpisodeFrontmatter` for MDX exports and `VideoEpisodeMeta` for the processed metadata object used by components. These interfaces enforce required fields like `youtubeId`, `title`, and optional `chapters` arrays, ensuring type safety across the ingestion and rendering pipeline.

### Where should supplementary files like code samples be stored?

Supplementary assets belong in the `videos/<slug>/kit/` directory parallel to the episode content. The [`lib/videos.ts`](https://github.com/woosal1337/blog/blob/main/lib/videos.ts) module exposes `listKitFiles()` for discovery and `readKitFile()` for content retrieval, while `kitFileHref()` in [`lib/video-utils.ts`](https://github.com/woosal1337/blog/blob/main/lib/video-utils.ts) generates canonical URLs for linking to these resources from MDX content.

### How does the system handle YouTube chapter navigation?

The [`lib/video-utils.ts`](https://github.com/woosal1337/blog/blob/main/lib/video-utils.ts) module provides `chapterSeconds()` to convert timestamp strings (e.g., "03:45") into seconds, and `chapterUrl(youtubeId, startSeconds)` to generate deep links that start playback at specific timestamps. The [`components/blocks/youtube-facade.tsx`](https://github.com/woosal1337/blog/blob/main/components/blocks/youtube-facade.tsx) component consumes these utilities to render clickable chapter links alongside the video player.

### What is required to add a new video episode to the blog?

Create a folder in `app/(website)/videos/(episode)/<slug>/` containing a `page.mdx` file that exports a `meta` object matching `VideoEpisodeFrontmatter`. Optionally add a `kit/` subdirectory at `videos/<slug>/kit/` for supplementary files. The `listSlugs()` function in [`lib/videos.ts`](https://github.com/woosal1337/blog/blob/main/lib/videos.ts) automatically discovers the new episode, requiring no manual route registration or build configuration changes.