---
title: "Migrate to Payload CMS: Seamless Swap from Dummy Data"
slug: "migrate-to-payload-cms-swap-dummy-data"
published: "2025-11-21"
updated: "2025-12-25"
categories:
  - "Payload"
tags:
  - "migrate to Payload CMS"
  - "Payload CMS migration"
  - "Payload integration"
  - "design-driven development"
  - "@payload-types"
  - "getPage getBlock utilities"
  - "block renderer"
  - "example data to Payload"
  - "Next.js Payload"
  - "frontend types"
  - "Payload API queries"
llm-intent: "reference"
audience-level: "intermediate"
framework-versions:
  - "payload cms"
  - "next.js"
  - "typescript"
  - "@payload-types"
  - "react"
status: "stable"
llm-purpose: "Migrate to Payload CMS: swap dummy data for Payload API queries, preserve your frontend types and components, and add getPage/getBlock helpers—read the…"
llm-prereqs:
  - "Access to Payload CMS"
  - "Access to Next.js"
  - "Access to TypeScript"
  - "Access to @payload-types"
  - "Access to React"
llm-outputs:
  - "Migrated Docker stack running on target VPS"
  - "Validated volume backups and restored data"
---

**Summary Triples**
- (Frontend types, serve as, the specification for Payload field configuration (design-driven development))
- (Payload-generated types (@payload-types), must match, your manual frontend types before swapping the data source)
- (Migration change, is limited to, replacing example data imports with Payload queries in pages (components unchanged))
- (Components, require no refactor if, the incoming data structure matches your frontend types)
- (getPage/getBlock utilities, are used to, map Payload API responses into the existing page/block props and preserve typing)
- (Verification step, includes, comparing manual interfaces (e.g., FeaturedIndustriesBlock) to Payload block/collection config)
- (Typical migration steps, include, verify types, generate @payload-types, replace example imports with fetch queries, add getPage/getBlock helpers)

### {GOAL}
Migrate to Payload CMS: swap dummy data for Payload API queries, preserve your frontend types and components, and add getPage/getBlock helpers—read the…

### {PREREQS}
- Access to Payload CMS
- Access to Next.js
- Access to TypeScript
- Access to @payload-types
- Access to React

### {STEPS}
1. Verify frontend types match Payload
2. Keep components unchanged
3. Replace example imports with queries
4. Create Payload helper utilities
5. Implement graceful fallbacks
6. Handle unknown block types
7. Optimize Payload queries and caching
8. Run verification and tests

<!-- llm:goal="Migrate to Payload CMS: swap dummy data for Payload API queries, preserve your frontend types and components, and add getPage/getBlock helpers—read the…" -->
<!-- llm:prereq="Access to Payload CMS" -->
<!-- llm:prereq="Access to Next.js" -->
<!-- llm:prereq="Access to TypeScript" -->
<!-- llm:prereq="Access to @payload-types" -->
<!-- llm:prereq="Access to React" -->
<!-- llm:output="Migrated Docker stack running on target VPS" -->
<!-- llm:output="Validated volume backups and restored data" -->

# Migrate to Payload CMS: Seamless Swap from Dummy Data
> Migrate to Payload CMS: swap dummy data for Payload API queries, preserve your frontend types and components, and add getPage/getBlock helpers—read the…
Matija Žiberna · 2025-11-21

> Part 7 of the [Design to Code](/blog/design-driven-block-systems) series — Following [Quick Reference](/blog/block-component-templates-checklists)

You've built your entire frontend with custom types and example data. Now it's time to integrate Payload CMS. Here's the good news: your code barely changes. You're just swapping the data source.

This guide shows how smooth the transition is.

## Why Your Code Doesn't Change

The magic of design-driven development is that **your types are your specification**. When you integrated with Payload, your types became the source of truth for Payload's field configuration.

Result: Your frontend types match Payload's output exactly. When you switch from example data to Payload data, the component sees the same type structure. No refactoring needed.

## The Big Picture

```
BEFORE (Dummy Data)
├─ Types: src/types/blocks/, src/types/collections/
├─ Example Data: example.ts files
├─ Components: Use example data
└─ Page: Imports example data

         ↓↓↓ ONE CHANGE ↓↓↓

AFTER (Payload Integration)
├─ Types: @payload-types (Payload generates them)
├─ Data: Payload CMS queries
├─ Components: Use Payload data (same structure!)
└─ Page: Queries Payload
```

The component code stays identical.

## Step 1: Verify Your Types Match Payload

Before switching to Payload, make sure your frontend types match what Payload will generate.

Your manual type:

```typescript
export interface FeaturedIndustriesBlock {
  blockType: 'featuredIndustries';
  template: 'default';
  title?: string;
  selectedIndustries?: Industry[];
  bgColor?: 'white' | 'light' | 'dark';
}

export interface Industry {
  id: number;
  title: string;
  slug?: string;
  description?: string;
  icon?: string;
  link?: CTA;
}
```

Should match your Payload collection/block config:

```typescript
// payload/blocks/featured-industries.ts
{
  slug: 'featured-industries',
  fields: [
    { name: 'title', type: 'text' },
    { name: 'selectedIndustries', type: 'relationship', relationTo: 'industries' },
    { name: 'bgColor', type: 'select', options: ['white', 'light', 'dark'] },
  ]
}

// payload/collections/industries.ts
{
  slug: 'industries',
  fields: [
    { name: 'title', type: 'text', required: true },
    { name: 'slug', type: 'text' },
    { name: 'description', type: 'textarea' },
    { name: 'icon', type: 'text' },
    { name: 'link', type: 'relationship', relationTo: 'ctas' },
  ]
}
```

The structure should be identical. If Payload has extra fields, that's fine—your components just won't use them. If Payload is missing fields, you have a mismatch to fix.

## Step 2: Prepare Your Component

Your component doesn't change. It already expects the exact type Payload will provide.

```typescript
// This component works with BOTH example data and Payload data
// No changes needed!

export function FeaturedIndustriesTemplate1({ data }: { data: FeaturedIndustriesBlock }) {
  const { selectedIndustries = [] } = data;

  return (
    <section>
      {selectedIndustries.map(industry => (
        <Card key={industry.id}>
          <h3>{industry.title}</h3>
          {/* ... render industry data ... */}
        </Card>
      ))}
    </section>
  );
}
```

This component works with:

```typescript
// Before: example data
const data = {
  blockType: 'featuredIndustries',
  selectedIndustries: industriesData,
};

// After: Payload data
const data = await getBlock('featured-industries', id);

// Same type, same component, works in both cases!
```

## Step 3: Update Data Fetching

Replace your example data with Payload queries.

Before:

```typescript
// src/app/page.tsx
import homePageData from './data';

export default function HomePage() {
  return (
    <main>
      {homePageData.layout.map((block, i) => (
        <BlockRenderer key={i} block={block} />
      ))}
    </main>
  );
}
```

After:

```typescript
// src/app/page.tsx
import { getPage } from '@/lib/payload';
import { BlockRenderer } from '@/components/block-renderer';

export default async function HomePage() {
  // Query Payload CMS instead of importing example data
  const homePageData = await getPage('home');

  return (
    <main>
      {homePageData.layout.map((block, i) => (
        <BlockRenderer key={i} block={block} />
      ))}
    </main>
  );
}
```

That's it. Everything else stays the same.

## Step 4: Create Payload Utilities

Build helper functions to query Payload data:

File: `src/lib/payload.ts`

```typescript
import type { Page, FeaturedIndustriesBlock } from '@payload-types';

const PAYLOAD_API = process.env.NEXT_PUBLIC_PAYLOAD_API_URL;

/**
 * Get a complete page with all blocks
 */
export async function getPage(slug: string): Promise<Page> {
  const response = await fetch(
    `${PAYLOAD_API}/api/pages?where[slug][equals]=${slug}`,
    { next: { revalidate: 60 } }
  );

  if (!response.ok) throw new Error(`Failed to fetch page: ${slug}`);

  const data = await response.json();
  return data.docs[0];
}

/**
 * Get a single block by ID
 */
export async function getBlock(blockType: string, id: string) {
  const response = await fetch(
    `${PAYLOAD_API}/api/blocks?where[blockType][equals]=${blockType}&where[id][equals]=${id}`,
    { next: { revalidate: 60 } }
  );

  if (!response.ok) throw new Error(`Failed to fetch block: ${blockType}/${id}`);

  const data = await response.json();
  return data.docs[0];
}

/**
 * Get all industries (collection)
 */
export async function getIndustries(): Promise<Industry[]> {
  const response = await fetch(
    `${PAYLOAD_API}/api/industries`,
    { next: { revalidate: 60 } }
  );

  if (!response.ok) throw new Error('Failed to fetch industries');

  const data = await response.json();
  return data.docs;
}

/**
 * Get single industry by slug
 */
export async function getIndustry(slug: string): Promise<Industry> {
  const response = await fetch(
    `${PAYLOAD_API}/api/industries?where[slug][equals]=${slug}`,
    { next: { revalidate: 60 } }
  );

  if (!response.ok) throw new Error(`Failed to fetch industry: ${slug}`);

  const data = await response.json();
  return data.docs[0];
}
```

These utilities handle Payload API calls consistently. Your components don't know about Payload—they just get data.

## Step 5: Update Page Data Functions

For pages that reference specific industries or other collections:

Before:

```typescript
// src/app/industries/[slug]/page.tsx
import { industriesData } from '@/types/collections';

export default function IndustryPage({ params }) {
  const industry = industriesData.find(i => i.slug === params.slug);

  if (!industry) return <div>Not found</div>;

  return <IndustryDetail industry={industry} />;
}
```

After:

```typescript
// src/app/industries/[slug]/page.tsx
import { getIndustry } from '@/lib/payload';

export default async function IndustryPage({ params }) {
  try {
    const industry = await getIndustry(params.slug);
    return <IndustryDetail industry={industry} />;
  } catch (error) {
    return <div>Not found</div>;
  }
}
```

Same component (`IndustryDetail`), different data source.

## Step 6: Update BlockRenderer (Optional)

If you're using Payload's block system, BlockRenderer might query blocks from Payload instead of receiving them as props:

Before:

```typescript
export function BlockRenderer({ block }: { block: Block }) {
  if (block.blockType === 'hero') {
    return <HeroTemplate1 data={block} />;
  }
  // ... etc
}
```

**After (no change):**

The BlockRenderer stays exactly the same. It doesn't know or care where blocks come from. It just renders them.

## Step 7: Handle Dynamic Imports (If Needed)

If Payload has new block types you haven't built yet, handle them gracefully:

```typescript
export function BlockRenderer({ block }: { data: Block }) {
  // Map blockType to component
  const component = blockComponentMap[block.blockType]?.[block.template];

  if (!component) {
    return (
      <div className="p-6 bg-yellow-50 border border-yellow-200">
        <p className="text-yellow-800">
          Block type "{block.blockType}" / "{block.template}" not yet implemented
        </p>
      </div>
    );
  }

  return component(block);
}
```

This prevents the app from breaking if Payload has a block type your frontend doesn't support yet.

## Migration Checklist

```
PAYLOAD MIGRATION CHECKLIST:

SETUP
□ Payload CMS installed and configured
□ Types exported from Payload (@payload-types)
□ Environment variables set (API URL, tokens)
□ Test Payload API connection with curl/postman

VERIFICATION
□ Frontend types match Payload schema
□ All required fields exist in Payload
□ Example data still works (verify nothing broke)
□ BlockRenderer still routes correctly

UTILITIES
□ Created src/lib/payload.ts with helper functions
□ getPage() function works
□ getBlock() function works
□ Collection query functions (getIndustries(), etc.) work

MIGRATION
□ Updated src/app/page.tsx to use getPage()
□ Updated collection pages to use get() functions
□ Removed imports of example data
□ Verified all pages render with Payload data
□ No console errors
□ No TypeScript errors

TESTING
□ Home page loads from Payload
□ All blocks render correctly
□ Detail pages load from Payload
□ Images and media display correctly
□ Links and CTAs work
□ No 404s or missing data
□ Performance is acceptable

FALLBACKS
□ Graceful error handling for missing data
□ 404 pages for not-found content
□ Empty states for no data
□ Development mode still works with example data
```

## Keeping Development with Examples

While integrating Payload, you might want to keep development mode using example data. You can add a flag:

```typescript
// src/lib/payload.ts
const USE_EXAMPLES = process.env.NEXT_PUBLIC_USE_EXAMPLES === 'true';

export async function getPage(slug: string) {
  if (USE_EXAMPLES) {
    // Return example data in development
    return homePageData;
  }

  // Query Payload in production
  const response = await fetch(`${PAYLOAD_API}/api/pages?where[slug][equals]=${slug}`);
  // ... etc
}
```

Then set `NEXT_PUBLIC_USE_EXAMPLES=true` in `.env.local` for local development.

## Rollback Strategy

If something goes wrong, you can quickly rollback:

```typescript
// src/lib/payload.ts
export async function getPage(slug: string) {
  try {
    // Query Payload
    const response = await fetch(`${PAYLOAD_API}/api/pages?where[slug][equals]=${slug}`);
    if (!response.ok) throw new Error('Payload error');
    return await response.json();
  } catch (error) {
    // Fallback to example data
    console.warn('Fallback to example data:', error);
    return homePageData;
  }
}
```

This way, if Payload is down, your site still works with example data.

## Performance Considerations

Payload queries should be efficient:

Revalidation:
```typescript
// Revalidate every 60 seconds (good for mostly-static sites)
{ next: { revalidate: 60 } }

// Revalidate every hour
{ next: { revalidate: 3600 } }

// Never revalidate (manually trigger with revalidatePath)
{ cache: 'no-store' }
```

Selective Fields:
```typescript
// Only query fields you need (not everything)
await fetch(`${PAYLOAD_API}/api/pages?select=slug,title,layout&where[slug][equals]=home`)
```

Pagination:
```typescript
// Limit results if querying collections
await fetch(`${PAYLOAD_API}/api/industries?limit=100&page=1`)
```

## Key Points to Remember

1. Your components don't change - They expect the exact type Payload provides
2. Your types guide Payload schema - Payload should match your frontend types
3. Data source changes, not the code - Just swap example data for Payload queries
4. BlockRenderer stays the same - It doesn't know or care where blocks come from
5. Fallbacks prevent broken sites - Always have a graceful fallback

## The Real Magic

This is where design-driven development truly shines. By letting design determine your data structure before you even built Payload, you ensured that when Payload was added, it matched perfectly.

No refactoring. No "we need to add a field." No "the CMS output doesn't match our frontend types." Just seamless integration.

---

## You've Completed the Series!

Congratulations! You now have the complete path: design-first development → custom types → components → example data → Payload integration.

Series Summary:
- [Part 1: Design-Driven Development](/blog/design-driven-development-build-types-from-figma) — Philosophy & Mindset
- [Part 2: Use Existing Payload Block Types](/blog/payload-cms-use-existing-block-types) — Working with existing definitions
- [Part 3: Create Custom Block Types](/blog/create-custom-block-types-payload-cms) — Building from scratch
- [Part 4: Creating Collections](/blog/creating-collections-reusable-data-entities) — Reusable data entities
- [Part 5: Icons, Components & Best Practices](/blog/lucide-react-shadcn-ui-cta-best-practices) — Consistency patterns
- [Part 6: Quick Reference](/blog/block-component-templates-checklists) — Templates & checklists
- [Back to Hub: Design to Code](/blog/design-driven-block-systems) — Full series overview

Next Resources:
- Need help with specific Payload config? Check Payload docs at [payloadcms.com](https://payloadcms.com)
- Questions about queries? See your Payload API docs
- Performance optimization? Review Next.js caching docs

Each step builds on the previous, and switching to Payload requires almost no code changes. This is the efficiency of design-driven development.

## LLM Response Snippet
```json
{
  "goal": "Migrate to Payload CMS: swap dummy data for Payload API queries, preserve your frontend types and components, and add getPage/getBlock helpers—read the…",
  "responses": [
    {
      "question": "What does the article \"Migrate to Payload CMS: Seamless Swap from Dummy Data\" cover?",
      "answer": "Migrate to Payload CMS: swap dummy data for Payload API queries, preserve your frontend types and components, and add getPage/getBlock helpers—read the…"
    }
  ]
}
```