# How Impeccable's Cloudflare Pages Deployment Works with Static API Data

> Learn how Impeccable deploys to Cloudflare Pages by pre-generating static JSON files and using edge rewrite rules for fast API endpoints, avoiding serverless cold starts.

- Repository: [Paul Bakaus/impeccable](https://github.com/pbakaus/impeccable)
- Tags: how-to-guide
- Published: 2026-03-09

---

**Impeccable deploys to Cloudflare Pages by pre-generating static JSON files during the build process and using edge rewrite rules to serve them as dynamic API endpoints, eliminating serverless function cold starts.**

The **pbakaus/impeccable** repository implements a static-site architecture that mimics a live API using pre-built JSON assets. By generating all API responses at build time and leveraging Cloudflare Pages' `_redirects` configuration, the application serves cached edge responses for typical API routes while reserving Pages Functions only for binary downloads.

## Build Pipeline Architecture

The deployment strategy centers on [`scripts/build.js`](https://github.com/pbakaus/impeccable/blob/main/scripts/build.js), which orchestrates the creation of static assets and Cloudflare-specific configuration files. This script transforms markdown skill definitions into a queryable JSON API during the build phase rather than at runtime.

### Generating the Static API

The `generateApiData()` function (lines 88-119 in [`scripts/build.js`](https://github.com/pbakaus/impeccable/blob/main/scripts/build.js)) serializes skill metadata into four distinct JSON structures:

```javascript
// scripts/build.js
function generateApiData(buildDir, skills, patterns) {
  const apiDir = path.join(buildDir, '_data', 'api');
  fs.mkdirSync(apiDir, { recursive: true });

  // skills.json – catalogue of all skills
  const skillsData = skills.map(s => ({
    id: path.basename(path.dirname(s.filePath)),
    name: s.name,
    description: s.description,
    userInvokable: s.userInvokable,
  }));
  fs.writeFileSync(path.join(apiDir, 'skills.json'), JSON.stringify(skillsData));

  // commands.json – only user-invokable skills
  const commandsData = skillsData.filter(s => s.userInvokable);
  fs.writeFileSync(path.join(apiDir, 'commands.json'), JSON.stringify(commandsData));

  // patterns.json – the pattern taxonomy
  fs.writeFileSync(path.join(apiDir, 'patterns.json'), JSON.stringify(patterns));

  // command-source/{id}.json – raw markdown source per skill
  const cmdSourceDir = path.join(apiDir, 'command-source');
  fs.mkdirSync(cmdSourceDir, { recursive: true });
  for (const skill of skills) {
    const id = path.basename(path.dirname(skill.filePath));
    const content = fs.readFileSync(skill.filePath, 'utf-8');
    fs.writeFileSync(path.join(cmdSourceDir, `${id}.json`), JSON.stringify({ content }));
  }

  console.log(`✓ Generated static API data (${skillsData.length} skills, ${commandsData.length} commands)`);
}

```

This function reads source files from `source/skills/*` and writes the processed output to `build/_data/api/`, creating a file-based database that Cloudflare Pages can serve directly from its edge cache.

### Configuring Edge Rewrites and Headers

The `generateCFConfig()` function (lines 33-57) generates the `_headers` and `_redirects` files that enable the static API illusion:

```javascript
// scripts/build.js
function generateCFConfig(buildDir) {
  const headers = `/*
  X-Content-Type-Options: nosniff
  X-Frame-Options: DENY

  /api/*
    Cache-Control: public, s-maxage=86400, stale-while-revalidate=3600

  /_data/api/*
    Cache-Control: public, s-maxage=86400, stale-while-revalidate=3600
  `;
  fs.writeFileSync(path.join(buildDir, '_headers'), headers);

  const redirects = `/api/skills /_data/api/skills.json 200
  /api/commands /_data/api/commands.json 200
  /api/patterns /_data/api/patterns.json 200
  /api/command-source/:id /_data/api/command-source/:id.json 200
  `;
  fs.writeFileSync(path.join(buildDir, '_redirects'), redirects);

  console.log('✓ Generated Cloudflare Pages config (_headers, _redirects)');
}

```

The **200 status code** in the redirect rules is critical—it instructs Cloudflare Pages to rewrite the URL internally rather than issue an HTTP redirect, preserving the `/api/` path in the browser while serving the static JSON file.

## Runtime Behavior on Cloudflare Pages

When deployed, Cloudflare Pages handles different request types through distinct mechanisms:

- **Static API requests** (e.g., `/api/skills`) match the rewrite rules in `_redirects` and serve the corresponding JSON from `build/_data/api/` with long-term edge caching.
- **Binary downloads** (e.g., `/api/download/...`) route to **Pages Functions** located in `functions/api/download/[type]/[provider]/[id].js`, which access bundled files via `env.ASSETS.fetch()`.

The build script copies provider-specific bundles into `build/_data/dist` via `copyDistToBuild()`, making them available to Functions through the Cloudflare Assets binding:

```javascript
// functions/api/download/[type]/[provider]/[id].js
export async function onRequest(context) {
  const { ASSETS } = context.env;               // Cloudflare Pages Assets
  const path = `/_data/dist/${params.provider}/${params.id}`;
  const asset = await ASSETS.fetch(path);
  return new Response(await asset.arrayBuffer(), {
    headers: { 'Content-Type': asset.headers.get('Content-Type') },
  });
}

```

This hybrid approach ensures that read-heavy API traffic hits cached static assets, while dynamic download requests execute only when necessary.

## End-to-End Deployment Workflow

The complete **Impeccable Cloudflare Pages deployment** follows this sequence:

1. **Build initiation**: `bun run build` executes [`scripts/build.js`](https://github.com/pbakaus/impeccable/blob/main/scripts/build.js).
2. **Data processing**: Skills are read from `source/skills/`, transformed for each provider, and written to `dist/`.
3. **API generation**: `generateApiData` creates the static JSON structure under `build/_data/api/`.
4. **Asset staging**: `copyDistToBuild` copies the `dist/` folder into `build/_data/dist`.
5. **Configuration**: `generateCFConfig` writes `_headers` and `_redirects` to the build root.
6. **Deployment**: Cloudflare Pages deploys the `build/` folder as specified in [`wrangler.toml`](https://github.com/pbakaus/impeccable/blob/main/wrangler.toml):

```toml

# wrangler.toml

name = "impeccable-style"
compatibility_date = "2024-12-01"
pages_build_output_dir = "./build"

```

7. **Runtime serving**: Requests to `https://<site>.pages.dev/api/skills` are rewritten to [`_data/api/skills.json`](https://github.com/pbakaus/impeccable/blob/main/_data/api/skills.json) and served from Cloudflare's edge cache with a 24-hour TTL.

## Summary

- **Static generation** occurs during the build phase via `generateApiData()` in [`scripts/build.js`](https://github.com/pbakaus/impeccable/blob/main/scripts/build.js), creating JSON files for skills, commands, and patterns.
- **Edge rewrites** configured in `_redirects` map `/api/*` routes to static files using 200-status internal rewrites.
- **Aggressive caching** is applied via `_headers`, setting `s-maxage=86400` and `stale-while-revalidate` for API endpoints.
- **Hybrid architecture** serves metadata from static assets and binary downloads via Pages Functions using `env.ASSETS.fetch()`.
- **Zero cold starts** for API reads because static assets are served directly from Cloudflare's cache without invoking serverless functions.

## Frequently Asked Questions

### How does Impeccable handle dynamic API requests on a static platform?

Impeccable eliminates truly dynamic API requests by pre-generating all possible JSON responses during the build process. The `_redirects` file uses 200-status rewrites to mask the static file paths, so clients interact with `/api/skills` while Cloudflare serves the pre-built [`_data/api/skills.json`](https://github.com/pbakaus/impeccable/blob/main/_data/api/skills.json) from its edge cache.

### What is the purpose of the `_headers` file in the build output?

The `_headers` file configures HTTP response headers for the static assets. It applies security headers globally and sets `Cache-Control: public, s-maxage=86400, stale-while-revalidate=3600` specifically for `/api/*` routes, ensuring responses remain cached at Cloudflare's edge for 24 hours with a one-hour stale-while-revalidate window.

### Why does the build script copy files to both `dist/` and `build/_data/dist/`?

The `dist/` directory contains provider-specific skill bundles (e.g., `.claude/skills/...`) used for local development, while `build/_data/dist/` is the copy consumed by Cloudflare Pages Functions at runtime. The `copyDistToBuild()` function ensures these files are available within the Pages build output directory so that `env.ASSETS.fetch()` can retrieve them without external network requests.

### Can the static API data be updated without a full rebuild?

No. Because the API responses are static JSON files generated during the build process, any change to skill metadata in `source/skills/*` requires running `bun run build` again and redeploying to Cloudflare Pages. This trade-off prioritizes read performance and cacheability over real-time data updates.