Docs/Fetching Content

Fetching Content

Fetch posts, pages, and media from WordPress via the REST API.

Fetching WordPress Content

PhantomWP connects to your WordPress site's REST API to fetch content dynamically. Your WordPress site serves as the CMS backend while Astro provides a fast, modern frontend.

How It Works

┌─────────────────┐ REST API ┌─────────────────┐ │ WordPress │ ◄─────────────────► │ Astro Site │ │ (Your CMS) │ /wp-json/wp/v2/ │ (PhantomWP) │ │ │ │ │ │ • Posts │ │ • Static pages │ │ • Pages │ │ • Blog │ │ • Media │ │ • SSR routes │ │ • Custom Types │ │ • Fast delivery│ └─────────────────┘ └─────────────────┘

WordPress remains your content management system—you write, edit, and manage content there. PhantomWP fetches this content via the WordPress REST API and renders it on your Astro site.

Connecting Your WordPress Site

Step 1: Enter Your Site URL

In PhantomWP, open the WordPress Settings:

  1. Click the WordPress button in the toolbar
  2. Enter your WordPress site URL (e.g., https://yourblog.com)
  3. Click Test Connection

Step 2: Verify REST API Access

PhantomWP will verify:

  • ✅ REST API is accessible
  • ✅ Posts and pages are available
  • ✅ Media can be fetched

Step 3: Browse Your Content

Once connected, you can:

  • Browse posts, pages, and media from the Data Panel
  • Create pages that fetch specific content
  • Use the WordPress client library in your Astro components

Using the WordPress Client

PhantomWP generates a src/lib/wordpress.ts client for your site:

// Fetch all posts
const posts = await getPosts();

// Fetch a single post by slug
const post = await getPost('my-post-slug');

// Fetch pages
const pages = await getPages();

// Fetch categories
const categories = await getCategories();

Example: Blog Index Page

---
import { getPosts } from '../lib/wordpress';
import Layout from '../layouts/Layout.astro';

const posts = await getPosts({ perPage: 10 });
---

<Layout title="Blog">
  <h1>Latest Posts</h1>
  <ul>
    {posts.map(post => (
      <li>
        <a href={`/blog/${post.slug}`}>
          {post.title.rendered}
        </a>
      </li>
    ))}
  </ul>
</Layout>

Example: Single Post Page

---
import { getPost, getAllPosts } from '../lib/wordpress';
import Layout from '../layouts/Layout.astro';

export async function getStaticPaths() {
  const posts = await getAllPosts();
  return posts.map(post => ({
    params: { slug: post.slug },
  }));
}

const { slug } = Astro.params;
const post = await getPost(slug);
---

<Layout title={post.title.rendered}>
  <article>
    <h1 set:html={post.title.rendered} />
    <div set:html={post.content.rendered} />
  </article>
</Layout>

What's Available via REST API

Posts

  • Title, content, excerpt
  • Publication date, modified date
  • Author information
  • Featured image (via _embed)
  • Categories and tags
  • Custom fields (meta)
  • SEO data (if using Yoast/Rank Math)

Pages

  • Title, content
  • Page template
  • Parent page
  • Menu order
  • Custom fields

Media

  • Images with all sizes
  • Alt text and captions
  • File metadata
  • Source URLs

Taxonomies

  • Categories
  • Tags
  • Custom taxonomies

Custom Post Types

  • Any registered post type with show_in_rest: true
  • WooCommerce products
  • Portfolio items
  • Events
  • etc.

WordPress embeds featured images when you add _embed to your request:

const posts = await getPosts({ perPage: 10 });

// Access featured image
const featuredImage = post._embedded?.['wp:featuredmedia']?.[0];
if (featuredImage) {
  const imageUrl = featuredImage.source_url;
  const altText = featuredImage.alt_text;
}

The WordPress client includes helper functions:

import { getFeaturedImageUrl } from '../lib/wordpress';

const imageUrl = getFeaturedImageUrl(post, 'large');

SEO Data

If your WordPress site uses an SEO plugin, that data is available:

Yoast SEO

const seoData = post.yoast_head_json;
// { title, description, robots, og_title, og_image, schema, ... }

Rank Math

const title = post.rank_math_title;
const description = post.rank_math_description;

Build-Time vs Runtime Fetching

Fetch content at build time for best performance:

---
// This runs at build time
const posts = await getPosts();
---

Rebuild your site when content changes (or use incremental builds).

Server-Side Rendering

For frequently updated content, use SSR:

---
// astro.config.mjs: output: 'server'
// This runs on each request
const posts = await getPosts();
---

Caching Strategies

Build-Time Caching

Content is fetched once at build time and cached as static HTML.

ISR (Incremental Static Regeneration)

With Vercel or similar platforms:

  • Pages are regenerated on a schedule
  • Stale content is served while fresh content builds

On-Demand Revalidation

Trigger rebuilds when content changes:

  • Webhook from WordPress on post save
  • Manual "Publish" button in PhantomWP

Handling Private Content

For private posts or authenticated requests:

// In your WordPress client
const headers = {
  'Authorization': 'Basic ' + btoa('username:application_password')
};

Set up Application Passwords in WordPress (Users → Edit → Application Passwords).

Custom Post Types

Access any custom post type registered with REST API support:

// Fetch WooCommerce products
const products = await getCustomPostType('products');

// Fetch portfolio items
const portfolio = await getCustomPostType('portfolio');

Performance Tips

  1. Use _fields parameter — Only fetch the fields you need
  2. Paginate large collections — Don't fetch 1000 posts at once
  3. Cache responses — Implement response caching
  4. Use _embed — Get related data in one request

Example optimized request:

const posts = await getPosts({
  perPage: 10,
  fields: ['id', 'slug', 'title', 'excerpt', 'date'],
  embed: true,
});

Troubleshooting

REST API Not Accessible

  1. Check your site URL is correct
  2. Ensure REST API isn't disabled by a plugin
  3. Verify permalinks are not set to "Plain"

CORS Errors

Add to your WordPress .htaccess or use a plugin:

Header set Access-Control-Allow-Origin "*"

Or install the PhantomWP WordPress plugin which handles this.

Ensure you're using _embed in requests:

const posts = await getPosts({ embed: true });

Next Steps