> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/Mich9025/vencol-front/llms.txt
> Use this file to discover all available pages before exploring further.

# WordPress Integration

> Learn how the VENCOL Front Template integrates with WordPress REST API for dynamic content

## Overview

The VENCOL Front Template integrates with WordPress as a headless CMS, fetching content via the WordPress REST API. This approach combines the power of WordPress content management with the performance of a React frontend.

## WordPress API Configuration

### API Endpoint

The WordPress REST API is configured in `lib/wordpress.ts`:

```tsx wordpress.ts theme={null}
const WP_API_URL = 'https://cms.gobigagency.co/vencol/wp-json/wp/v2';
```

<Warning>
  Update this URL to point to your WordPress installation. Ensure the WordPress REST API is publicly accessible.
</Warning>

## Data Types

### WordPress Post Interface

Internal interface for raw WordPress API responses:

```tsx wordpress.ts theme={null}
interface WPPost {
  id: number;
  date: string;
  slug: string;
  title: { rendered: string };
  excerpt: { rendered: string };
  content: { rendered: string };
  featured_media: number;
  _embedded?: {
    'wp:featuredmedia'?: Array<{ source_url: string }>;
    'wp:term'?: Array<Array<{ name: string }>>;
  };
}
```

### Application Types

Application-friendly interfaces defined in `types.ts`:

```tsx types.ts theme={null}
export interface BlogPost {
  id: number;
  title: string;
  slug: string;
  excerpt: string;
  content: string;
  date: string;
  image: string;
  category: string;
}

export interface WPPage {
  id: number;
  slug: string;
  title: string;
  content: string;
  excerpt: string;
  date: string;
  image: string;
}
```

## Utility Functions

### HTML Stripping

WordPress returns titles and excerpts with HTML tags. This function cleans them:

```tsx wordpress.ts theme={null}
function stripHtml(html: string): string {
  const doc = new DOMParser().parseFromString(html, 'text/html');
  return doc.body.textContent?.trim() || '';
}
```

**Usage:**

```tsx theme={null}
const cleanTitle = stripHtml(post.title.rendered);
// Input: "Hello <em>World</em>"
// Output: "Hello World"
```

### Date Formatting

Converts WordPress date format to localized Spanish format:

```tsx wordpress.ts theme={null}
function formatDate(dateString: string): string {
  const date = new Date(dateString);
  return date.toLocaleDateString('es-ES', {
    year: 'numeric',
    month: 'short',
    day: 'numeric',
  });
}
```

**Example output:** `"3 mar 2026"`

## Data Mapping

### Blog Post Mapping

Transforms WordPress post format to application format:

```tsx wordpress.ts theme={null}
function mapWPPostToBlogPost(post: WPPost): BlogPost {
  const featuredImage =
    post._embedded?.['wp:featuredmedia']?.[0]?.source_url ||
    'https://picsum.photos/400/250';

  const category =
    post._embedded?.['wp:term']?.[0]?.[0]?.name || 'General';

  return {
    id: post.id,
    slug: post.slug,
    title: stripHtml(post.title.rendered),
    excerpt: stripHtml(post.excerpt.rendered),
    content: post.content.rendered,
    date: formatDate(post.date),
    image: featuredImage,
    category,
  };
}
```

<Info>
  Featured images and categories are extracted from the `_embedded` object when using the `_embed` query parameter.
</Info>

### Page Mapping

Transforms WordPress page format:

```tsx wordpress.ts theme={null}
function mapWPPageToWPPage(page: WPPost): WPPage {
  const featuredImage =
    page._embedded?.['wp:featuredmedia']?.[0]?.source_url ||
    '';

  return {
    id: page.id,
    slug: page.slug,
    title: stripHtml(page.title.rendered),
    content: page.content.rendered,
    excerpt: stripHtml(page.excerpt.rendered),
    date: formatDate(page.date),
    image: featuredImage,
  };
}
```

## API Functions

### Fetching Blog Posts

Retrieves multiple blog posts:

```tsx wordpress.ts theme={null}
export async function fetchBlogPosts(perPage = 10): Promise<BlogPost[]> {
  const res = await fetch(
    `${WP_API_URL}/posts?per_page=${perPage}&_embed`
  );

  if (!res.ok) {
    throw new Error(`WordPress API error: ${res.status}`);
  }

  const posts: WPPost[] = await res.json();
  return posts.map(mapWPPostToBlogPost);
}
```

**Usage in components:**

```tsx Home.tsx theme={null}
import { fetchBlogPosts } from '../lib/wordpress';
import { BlogPost } from '../types';

const [blogPosts, setBlogPosts] = useState<BlogPost[]>([]);

useEffect(() => {
  fetchBlogPosts()
    .then((wpPosts) => {
      if (wpPosts.length > 0) setBlogPosts(wpPosts);
    })
    .catch((err) => console.warn('WP blog fetch failed, using fallback:', err));
}, []);
```

### Fetching Pages by Slug

Retrieves a single page by its slug:

```tsx wordpress.ts theme={null}
export async function fetchWPPageBySlug(slug: string): Promise<WPPage | null> {
  const res = await fetch(
    `${WP_API_URL}/pages?slug=${encodeURIComponent(slug)}&_embed`
  );

  if (!res.ok) {
    throw new Error(`WordPress API error: ${res.status}`);
  }

  const pages: WPPost[] = await res.json();
  if (pages.length === 0) return null;
  return mapWPPageToWPPage(pages[0]);
}
```

**Usage in PageDetail component:**

```tsx PageDetail.tsx theme={null}
const { slug } = useParams<{ slug: string }>();
const [page, setPage] = useState<WPPage | null>(null);
const [loading, setLoading] = useState(true);
const [notFound, setNotFound] = useState(false);

useEffect(() => {
  if (!slug) return;
  setLoading(true);
  setNotFound(false);

  fetchWPPageBySlug(slug)
    .then((wpPage) => {
      if (wpPage) {
        setPage(wpPage);
      } else {
        setNotFound(true);
      }
    })
    .catch((err) => {
      console.warn('Error fetching WP page:', err);
      setNotFound(true);
    })
    .finally(() => setLoading(false));
}, [slug]);
```

## WordPress REST API Parameters

### Embedding Related Data

The `_embed` parameter includes related data in the response:

```
/wp-json/wp/v2/posts?_embed
```

This embeds:

* Featured images (`wp:featuredmedia`)
* Author information (`author`)
* Categories and tags (`wp:term`)

### Common Query Parameters

| Parameter  | Description                | Example            |
| ---------- | -------------------------- | ------------------ |
| `per_page` | Number of items to return  | `per_page=10`      |
| `page`     | Page number for pagination | `page=2`           |
| `slug`     | Filter by slug             | `slug=hello-world` |
| `_embed`   | Include embedded data      | `_embed`           |
| `orderby`  | Order results by field     | `orderby=date`     |
| `order`    | Sort order                 | `order=desc`       |

**Example:**

```tsx theme={null}
// Get 5 most recent posts with embedded data
fetch(`${WP_API_URL}/posts?per_page=5&_embed&orderby=date&order=desc`)
```

## Error Handling

### Graceful Degradation

The application uses fallback data when WordPress is unavailable:

```tsx Home.tsx theme={null}
const [blogPosts, setBlogPosts] = useState<BlogPost[]>(siteContent.blog.posts);

useEffect(() => {
  fetchBlogPosts()
    .then((wpPosts) => {
      if (wpPosts.length > 0) setBlogPosts(wpPosts);
    })
    .catch((err) => console.warn('WP blog fetch failed, using fallback:', err));
}, []);
```

<Tip>
  Always initialize state with fallback data from `data.tsx` to ensure the site works even if WordPress is down.
</Tip>

### 404 Handling

When a page is not found:

```tsx PageDetail.tsx theme={null}
if (notFound || !page) {
  return (
    <div className="min-h-screen flex items-center justify-center">
      <SEO title="Página no encontrada" />
      <div className="text-center">
        <h2>Página no encontrada</h2>
        <Link to="/">Volver al Inicio</Link>
      </div>
    </div>
  );
}
```

## Loading States

Display loading skeleton while fetching:

```tsx PageDetail.tsx theme={null}
if (loading) {
  return (
    <div className="pt-48 pb-20 relative z-10">
      <div className="max-w-4xl mx-auto px-4">
        <div className="animate-pulse space-y-8">
          <div className="h-8 bg-white/10 rounded w-1/3" />
          <div className="h-[40vh] bg-white/5 rounded-2xl" />
          <div className="space-y-4">
            <div className="h-6 bg-white/10 rounded w-3/4" />
            <div className="h-4 bg-white/5 rounded w-full" />
            <div className="h-4 bg-white/5 rounded w-5/6" />
          </div>
        </div>
      </div>
    </div>
  );
}
```

## Rendering WordPress Content

### Dangerously Set HTML

WordPress content includes HTML that needs to be rendered:

```tsx PageDetail.tsx theme={null}
<div
  className="prose prose-invert prose-lg max-w-none
    prose-headings:text-white
    prose-p:text-gray-300
    prose-a:text-brand-green
    prose-img:rounded-xl
  "
  dangerouslySetInnerHTML={{ __html: page.content }}
/>
```

<Warning>
  Only use `dangerouslySetInnerHTML` with trusted content from your WordPress CMS. Never use it with user-generated content without sanitization.
</Warning>

### Prose Styling

Tailwind Typography plugin provides prose classes for rich content:

```tsx theme={null}
proseClasses="
  prose prose-invert prose-lg max-w-none
  prose-headings:text-white prose-headings:font-bold
  prose-h2:text-2xl prose-h2:border-b prose-h2:border-white/10
  prose-h3:text-xl prose-h3:text-brand-green
  prose-p:text-gray-300 prose-p:leading-8
  prose-strong:text-white
  prose-a:text-brand-green hover:prose-a:underline
  prose-blockquote:border-l-brand-green
  prose-img:rounded-xl prose-img:border prose-img:border-white/10
"
```

## WordPress Image Handling

### Featured Images

Featured images are extracted from embedded data:

```tsx theme={null}
const featuredImage =
  post._embedded?.['wp:featuredmedia']?.[0]?.source_url ||
  'https://picsum.photos/400/250'; // Fallback placeholder
```

### Image Alignment Classes

Custom CSS supports WordPress image alignment:

```css index.html theme={null}
.aligncenter { margin: 0 auto; display: block; }
.alignleft { float: left; margin-right: 1.5em; }
.alignright { float: right; margin-left: 1.5em; }
.wp-block-image img { max-width: 100%; height: auto; }
```

## SEO Integration

WordPress content is used for SEO metadata:

```tsx PageDetail.tsx theme={null}
<SEO
  title={page.title}
  description={page.excerpt}
  image={page.image}
  url={`${siteContent.meta.siteUrl}/${page.slug}`}
/>
```

## Best Practices

<AccordionGroup>
  <Accordion title="Always Use _embed Parameter">
    Include `_embed` in API requests to get featured images and categories in a single request:

    ```tsx theme={null}
    // Good - single request
    fetch(`${WP_API_URL}/posts?_embed`)

    // Bad - requires additional requests for media
    fetch(`${WP_API_URL}/posts`)
    ```
  </Accordion>

  <Accordion title="Implement Fallback Data">
    Always provide fallback data for when WordPress is unavailable:

    ```tsx theme={null}
    const [posts, setPosts] = useState<BlogPost[]>(fallbackPosts);

    useEffect(() => {
      fetchBlogPosts()
        .then(setPosts)
        .catch(() => console.warn('Using fallback'));
    }, []);
    ```
  </Accordion>

  <Accordion title="Handle Loading States">
    Implement proper loading UI to improve perceived performance:

    ```tsx theme={null}
    if (loading) return <LoadingSkeleton />;
    if (notFound) return <NotFoundPage />;
    return <Content data={page} />;
    ```
  </Accordion>

  <Accordion title="Sanitize Content">
    WordPress content is trusted in this application, but if you allow user-generated content, implement proper sanitization before rendering HTML.
  </Accordion>

  <Accordion title="Use TypeScript Types">
    Define proper TypeScript interfaces for all WordPress data:

    ```tsx theme={null}
    interface WPPost { /* ... */ }
    interface BlogPost { /* ... */ }

    function mapPost(wp: WPPost): BlogPost { /* ... */ }
    ```
  </Accordion>
</AccordionGroup>

## Extending the Integration

### Adding Custom Post Types

To fetch custom post types:

```tsx theme={null}
export async function fetchCustomPosts(): Promise<CustomPost[]> {
  const res = await fetch(
    `${WP_API_URL}/custom-post-type?per_page=10&_embed`
  );
  
  if (!res.ok) throw new Error(`API error: ${res.status}`);
  
  const posts = await res.json();
  return posts.map(mapCustomPost);
}
```

### Adding Custom Fields

If using ACF or custom fields:

```tsx theme={null}
interface WPPostWithACF extends WPPost {
  acf?: {
    custom_field: string;
    another_field: number;
  };
}

function mapPostWithACF(post: WPPostWithACF): BlogPost {
  return {
    ...mapWPPostToBlogPost(post),
    customField: post.acf?.custom_field || '',
  };
}
```

### Pagination Support

Implement pagination for blog listing:

```tsx theme={null}
export async function fetchBlogPosts(
  perPage = 10,
  page = 1
): Promise<{ posts: BlogPost[]; totalPages: number }> {
  const res = await fetch(
    `${WP_API_URL}/posts?per_page=${perPage}&page=${page}&_embed`
  );
  
  if (!res.ok) throw new Error(`API error: ${res.status}`);
  
  const posts = await res.json();
  const totalPages = parseInt(res.headers.get('X-WP-TotalPages') || '1');
  
  return {
    posts: posts.map(mapWPPostToBlogPost),
    totalPages,
  };
}
```

## Related Topics

<CardGroup cols={2}>
  <Card title="Architecture" icon="sitemap" href="/concepts/architecture">
    Understand the overall application structure
  </Card>

  <Card title="Routing" icon="route" href="/concepts/routing">
    Learn about dynamic page routing
  </Card>

  <Card title="Styling" icon="palette" href="/concepts/styling">
    Explore WordPress content styling
  </Card>
</CardGroup>
