If you want to build a completely custom frontend UI without giving up the powerful, built-in WordPress ecosystem, a headless setup is exactly what you need.
A headless WordPress architecture lets you use the WordPress CMS for content management while using the framework of your choice for the frontend. In this tutorial, we will use Astro, a modern framework built for speed that lets you fetch data from WordPress and render it as static HTML.
This combination provides the ultimate developer experience: you keep the familiar WordPress dashboard and plugin ecosystem while building a completely custom, high-performance frontend free from the limitations of legacy themes.
Benefits of Headless WordPress
Decoupling content creation and frontend development is a major benefit. Content teams use the familiar WordPress dashboard for posts and SEO without touching the Astro codebase, speeding up the content pipeline. Simultaneously, frontend developers focus purely on UX, features, and design optimization without interrupting content work.
Here are the primary advantages you can expect from this headless architecture:
- Performance: Because Astro builds statically by default, it fetches your data from the WordPress REST API at build time and generates static HTML files, making your site incredibly fast.
- Security: Your WordPress backend is not directly exposed through the frontend, reducing your attack surface while still allowing you to apply standard security controls where needed.
- Flexibility: You have full developer control over the frontend code while still using WordPress as a robust CMS.
- SEO: Static pages are easily crawled and indexed by search engines.
By the end of this tutorial, you will know exactly how to connect these two powerful tools. We will focus on producing static pages that are generated entirely at build time.
Once you are comfortable with this workflow, you can later customize how Astro behaves. For example, you can eventually change your category pages to dynamically fetch all pages via the API on the client side, rather than generating them during the build process.
Prerequisites
Before starting, you need a live WordPress site with the REST API accessible (at /wp-json/wp/v2/).
In this guide, we will not teach you how to install WordPress, as we have already covered this topic extensively in our previous blog posts. If you need help setting up your initial WordPress site, please refer to one of these guides:
- How to Install WordPress on Ubuntu
- How to Install WordPress on Docker in 2025 [Step-By-Step Guide]
- How To Install WordPress On OpenLiteSpeed (2026 Tutorial)
- How to Install WordPress with Apache on Ubuntu 2026
- How To Install WordPress With RunCloud | Step-By-Step Guide
Phase 1: Prepare WordPress for Headless
Once your WordPress site is live on the internet, the built-in WordPress REST API will already be active. We just need to make sure it is properly formatted and accessible to Astro.
- Set your Permalinks: The WordPress REST API relies on clean URLs to work properly.
- Log in to your WordPress admin dashboard.
- Navigate to Settings > Permalinks.
- Select “Post name” (or any other clean URL structure).
- Click Save Changes.
- Verify the REST API is working
- Open a new browser tab.
- Visit your WordPress site’s API endpoint at https://[YOUR-WP-DOMAIN]/wp-json/wp/v2/ (replace the bracketed text with your actual domain name).
- Check the screen for a JSON response containing the site data containing your posts. If you see this data, your headless backend is ready.

- Set up Custom Post Types (Optional)
If you use Custom Post Types such as “Portfolios” or “Testimonials”, they are hidden from the REST API by default (unless a plugin actively enables show_in_rest). To use a CPT in your headless setup alongside standard posts and pages, you must configure them in your WordPress dashboard to allow REST API support.
Note: The upcoming examples will fetch data from a WordPress site whose content types have already been configured and exposed to the REST API.
- Configure Authentication and Fetching Strategy (optional)
By default, the REST API is open to the public for reading content. If you are building a private application, you can configure your WordPress site to require authentication by following the official REST API Handbook.
Phase 2: Create WordPress Frontend with Astro
With your WordPress backend configured and its REST API ready to serve content, the next step is to build a fast frontend to display your posts. We’ll use Astro to fetch content from the API at build time, generating static HTML pages for maximum performance and security.
Step 1: Create a New Astro Project
With your WordPress site ready, create a new Astro project using the official starter template:
npm create astro@latest my-astro-wpSelect the following options when prompted:
- Use A basic, helpful starter project: Yes
- Install dependencies: Yes
- Initialize git: Yes
Navigate to your project folder:
cd my-astro-wpStep 2: Configure Environment Variables
Create a .env file in the project root to store your WordPress URL:
PUBLIC_WP_URL=https://runcloud.example.com/wp-json/wp/v2/Replace the URL with your actual WordPress site URL.
Step 3: Create the Homepage That Fetches WordPress Data
Create your homepage at src/pages/index.astro to fetch and display WordPress posts:
---
import Layout from '../layouts/Layout.astro';
interface WP_Post {
title: { rendered: string };
content: { rendered: string };
excerpt: { rendered: string };
slug: string;
_embedded?: {
'wp:featuredmedia'?: Array<{
media_details: {
sizes: {
medium?: { source_url: string };
};
};
}>;
};
}
const wpUrl = import.meta.env.PUBLIC_WP_URL;
const res = await fetch(`${wpUrl}/posts?_embed&per_page=20`);
const posts: WP_Post[] = await res.json();
---
<Layout title="Astro + WordPress Blog">
<main>
<h1>Latest Posts</h1>
<ul class="post-list">
{posts.map((post) => (
<li class="post-item">
{post._embedded?.['wp:featuredmedia']?.[0]?.media_details?.sizes?.medium?.source_url && (
<img
src={post._embedded['wp:featuredmedia'][0].media_details.sizes.medium.source_url}
alt={post.title.rendered}
class="post-thumbnail"
/>
)}
<h2>
<a href={`/posts/${post.slug}/`} set:html={post.title.rendered} />
</h2>
<p class="post-excerpt" set:html={post.excerpt.rendered} />
</li>
))}
</ul>
</main>
</Layout>
<style>
main {
max-width: 800px;
margin: 0 auto;
padding: 2rem;
}
h1 {
font-size: 2.5rem;
margin-bottom: 2rem;
text-align: center;
}
.post-list {
list-style: none;
padding: 0;
}
.post-item {
margin-bottom: 2rem;
padding: 1.5rem;
border: 1px solid #eee;
border-radius: 8px;
text-align: center;
}
.post-thumbnail {
width: 100%;
max-width: 400px;
height: auto;
border-radius: 4px;
margin-bottom: 1rem;
display: block;
margin-left: auto;
margin-right: auto;
}
.post-item h2 {
margin: 0.5rem 0;
}
.post-item h2 a {
color: #333;
text-decoration: none;
}
.post-item h2 a:hover {
color: #0066cc;
}
.post-excerpt {
color: #666;
}
</style>A note on pagination: The WordPress REST API limits the number of posts that can be returned in a single request. While per_page=20 works for small sites, larger sites will need pagination to fetch all posts.
The maximum value for per_page is typically 100. If you exceed this, the API will silently limit the results.
For production use, you should either:
- Fetch multiple pages using the
pageparameter - Implement a loop to retrieve all posts during the build process
Step 4: Create Dynamic Post Pages
Create src/pages/posts/[slug].astro to handle individual post pages:
---
import Layout from '../../layouts/Layout.astro';
interface WP_Post {
title: { rendered: string };
content: { rendered: string };
slug: string;
}
export async function getStaticPaths() {
const wpUrl = import.meta.env.PUBLIC_WP_URL;
const res = await fetch(`${wpUrl}/posts?_fields=slug,title,content`);
const posts: WP_Post[] = await res.json();
return posts.map((post) => ({
params: { slug: post.slug },
props: { post },
}));
}
const { post } = Astro.props;
---
<Layout title={post.title.rendered}>
<main>
<article>
<h1 set:html={post.title.rendered} />
<div class="content" set:html={post.content.rendered} />
<a href="/" class="back-link">← Back to all posts</a>
</article>
</main>
</Layout>
<style>
main {
max-width: 800px;
margin: 0 auto;
padding: 2rem;
}
article {
background: white;
padding: 2rem;
border-radius: 8px;
}
h1 {
font-size: 2.5rem;
margin-bottom: 2rem;
color: #333;
}
.content {
line-height: 1.8;
color: #444;
}
.content :global(img) {
max-width: 100%;
height: auto;
border-radius: 4px;
}
.content :global(p) {
margin-bottom: 1.5rem;
}
.back-link {
display: inline-block;
margin-top: 2rem;
color: #0066cc;
text-decoration: none;
}
.back-link:hover {
text-decoration: underline;
}
</style>Step 5: Run the Development Server
Start the development server:
npm run devVisit http://localhost:4321 to see your headless WordPress blog in action. You should see all your WordPress posts displayed on the homepage, and clicking on any post will take you to its full article page.
Step 6: Build for Production
When ready to deploy, build your static site:
npm run buildThe output will be in the dist/ folder, ready to deploy to any hosting provider, such as Netlify, GitHub Pages, or RunCloud.
Step 7: Keep Your Content in Sync with Rebuilds
The Astro project in the above example generates static pages at build time by default; any new or updated content published in WordPress will not appear on your live site immediately. To display the latest content, you must trigger a new build so Astro can fetch the fresh data from your REST API.
Depending on your publishing schedule and team size, you have a few ways to manage these rebuilds.
Option A: Manual Rebuilds (Simple)
If you only publish content occasionally, the simplest approach is to manually trigger a deployment from your RunCloud dashboard whenever you publish a new post.
- Log in to your RunCloud dashboard.
- Navigate to Atomic Deployment > your Astro project.
- Click Force Deploy to force a manual rebuild.
RunCloud will run your deployment script, fetch the newest WordPress data, and update your live site using atomic deployment.
Option B: Automated API Triggers (Advanced)
By default, RunCloud automatically builds your site whenever new code changes are pushed to GitHub. However, publishing a post in WordPress does not push code to GitHub. To automate deployments based on content updates, you can use the RunCloud API to trigger a build programmatically.
You might be tempted to add a custom PHP function to your WordPress site that pings the RunCloud API every time a post is saved. However, if you have a team of writers constantly saving drafts and making simultaneous revisions, this can result in dozens of unnecessary deployments running back-to-back, which can consume server resources and cause conflicts.
Scheduled Deployments (Cron Jobs)
To avoid overwhelming your deployment pipeline, the best practice is to set up a cron job. Instead of deploying on every single save, you can configure a server cron job to ping the RunCloud deployment API on a predictable schedule.
- Decide on a publishing schedule (for example, once every 12 hours or once a day at midnight).
- Create a server-level cron job that sends a POST request to your RunCloud Webhook URL at that specific interval.
- Instruct your content team that new posts will go live at those designated times.
Read the RunCloud documentation to learn more about enabling API access and setting up cron jobs on RunCloud.
Note: The need to constantly rebuild your application depends entirely on your Astro project architecture. The steps above apply to Static Site Generation (SSG), Astro’s default behavior that offers the best performance. However, if you configure Astro to use Server-Side Rendering (SSR), your application will dynamically fetch data from the WordPress API at runtime. In an SSR setup, any content changes saved in WordPress will appear on your frontend instantly, completely eliminating the need to rebuild your site after each post.
Phase 3: Deploy Astro Project to RunCloud
Now that your local Astro project is successfully fetching data from your headless WordPress setup, it is time to share it with the world.
In this phase, we will push your code to GitHub and set up a deployment pipeline on RunCloud.
We are going to configure an “atomic deployment.” The major benefit of this setup is that after you commit and push your code, it becomes available to users worldwide in often less than a minute. Furthermore, if a build fails for any reason, your old code will continue running smoothly with zero downtime.
Here is how to set up your professional deployment workflow.
Step 1: Push your Astro project to GitHub
First, we need to host your code in a repository so RunCloud can access it.
- Open your web browser and log in to your GitHub account (or any supported Git provider).
- Navigate to your dashboard, then click the New button to create a repository.
- Give your repository a name.
- Leave the option to add a README file unchecked. It is very important that the repository is completely empty.
- Click Create repository.
- GitHub will now show you a page with instructions for pushing an existing repository from the command line. Open your computer’s terminal, make sure you are in your Astro project folder, and copy and paste those specific commands to commit your code and push it to GitHub.

Step 2: Create a new RunCloud Web Application
Now, let’s tell RunCloud where to find your code.
- Log in to your RunCloud dashboard.
- Navigate to your server, then click Deploy New Web App.
- Choose the option to install from a Git repository and select your Git provider.
- Enter a suitable name for your application.
- Locate the “Web Application Owner” section and uncheck the “Use existing system user” checkbox.
- Enter a new name, such as
astrowp.

- Configure the domain name for your website. (You can use a test domain name for now and update it later manually or by using the RunCloud DNS manager.)
- Enter the details for your new project into the “Repository name” and “Branch name” fields.
- Copy the deployment key generated by RunCloud.
- Open your GitHub repository and navigate to Settings > Deploy keys.
- Click Add deploy key, paste the key provided by RunCloud, and save your changes.
- Return to RunCloud and leave all other settings at their defaults.
- Click Add Web Application.
Note: Steps for adding deployment keys can vary by provider. For exact steps, screenshots, and a detailed guide, check out your supported provider in the RunCloud documentation.
Step 3: Convert to Atomic Deployment and set Webhooks
Atomic deployments keep your site up while Astro builds your new pages.
- Inside your RunCloud dashboard, click on Atomic Deployment in the left menu.
- Click the “Add New Project” button to convert your application to atomic deployments.

- Follow the on-screen instructions to copy the provided webhook URL.
- Go back to your GitHub repository, navigate to Settings > Webhooks, and click Add webhook.
- Paste the RunCloud URL into the “Payload URL” field and save. This ensures that GitHub tells RunCloud to update your site whenever you push new code.
Step 4: Add your Environment Variables
Because your code is now on a live server, it needs your “.env” file to know where your WordPress API lives. RunCloud’s atomic deployment uses a shared folder so your environment variables persist across deployments.
- In your RunCloud Web Application dashboard, navigate to Atomic Deployment > Your Project > Symlink.
- Click Add New Symlink.
- Set the “Symlink Type” to “Config”.
- Enter “.env” in both the “Link From” and “Link To” fields.
- Add your PUBLIC_WP_URL variable exactly as you did on your local machine.
- Set a password for encryption. Remember this password if you want to edit this file again.
- Click Save.

Step 5: Install NVM via SSH
RunCloud servers ship with a default version of Node.js, but Astro often requires a specific, modern LTS (Long-Term Support) version. We will install Node Version Manager to handle this safely.
- Open your terminal and log in to your server via SSH using your new system user account (for example, “astrowp”). For step-by-step instructions and a detailed guide on connecting to your server via SSH, refer to the RunCloud documentation on How to Connect to Your Server via SSH.
- Run the following command to download and install NVM:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash- Close your terminal completely and open a new SSH session so the system recognizes the new software.
- Run nvm install –lts to install the latest long-term support version of Node.js.
Note: This is the only time you need to log in via SSH; all other processes will be handled automatically by the Git deployment pipeline.
Step 6: Create the Deployment Script
Now we will write the instructions that RunCloud follows every time it receives new code from GitHub.
- In your RunCloud dashboard, navigate to Atomic Deployment > Your Project > Deployment Script.
- Scroll down to the Activate Latest Release section and click “Add Script”.
- Give this script a suitable name, and under the “When to Run This Script”, select “Before Activate latest release” from the dropdown menu.
- Delete the default text and paste the following script:
# 1. Navigate to the current release directory
cd {RELEASEPATH}
# 2. Load NVM into the script environment
export NVM_DIR="/home/$USER/.nvm"[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
# 3. Tell the system to use the LTS version of Node.js
nvm use --lts
# 4. Install your Astro project dependencies
npm install
# 5. Build the static files using an absolute path to avoid version conflicts
npm run buildNote: By using the NVM in step 3, you ensure the system will not accidentally revert to RunCloud’s built-in Node version, preventing unexpected version conflicts.
- Make sure to check the box next to “Run on Web Application” and then click Save to apply your new script.
- After that, open the Settings tab for this atomic deployment project to configure the deployment configuration options.
- Auto deploy on git push: Triggers a deployment every time you push code to the configured branch.
- Install Composer dependencies: Ensure this is UNCHECKED. For this Astro project, we use npm install in the custom script, not Composer.
- Install Dev Dependencies: Installs development-related PHP dependencies. (Not applicable to this Node.js/Astro project).
- You can optionally configure notification channels such as Slack, Discord, Telegram, or Webhooks for both successful and failed deployments in the Notifications section of your RunCloud Web Application.

Step 7: Run your First Deployment
You are fully configured and ready to go.
- Still inside the Atomic Deployment menu on RunCloud, locate the option to manually run a deployment on the top right.
- Click Force Deploy.
- Watch the deployment log. You will see RunCloud fetch your code, install dependencies, and build your Astro HTML files using your WordPress data.

Once the build passes, your headless website is officially live. Now, if you visit the URL configured in Step 2 for your frontend web application on RunCloud, you will be able to view your newly deployed, fast, headless WordPress site powered by Astro.

Phase 4: Advanced Steps (optional)
Now that your headless WordPress and Astro website is live, you can explore a few optional advanced steps to optimize your workflow, improve performance, and expand your site features.
Create a Staging Environment
To take complete advantage of the RunCloud environment, it is highly recommended to build a staging environment.
A staging environment is a private replica of your website where you can test WordPress plugins, Astro code updates, or new designs without breaking your live production site.
Because your backend (WordPress) and frontend (Astro) are separated, you will manage their staging environments separately. You can deploy two different branches of your Astro Git repository for this purpose.
Test your staging site locally:
- Clone your production WordPress site to a new staging domain on RunCloud using RunCloud’s built-in one-click WordPress staging functionality.
- Open your local Astro project on your computer.
- Open your “.env” file.
- Update the
PUBLIC_WP_URLvariable to match your new staging WordPress domain name. - Run your local development server to safely test your new WordPress plugins against your Astro frontend.
Create a cloud staging environment for your team:
Optionally, you can create a second project on the cloud so your entire team can test changes under load or for longer durations.
- Open your GitHub repository and create a new branch named “staging”.
- Log in to your RunCloud dashboard and navigate to Web Application.
- Click Create Web App.
- Follow the standard deployment steps, but enter “staging” into the “Branch name” field.
- Assign a test domain name to this new web application and click Add Web Application.
Install RunCache
To make your headless website build even faster, you should pair it with an object caching plugin. Object caching saves the results of complex database queries and returns data instantly. This significantly speeds up your WordPress REST API responses.
RunCache is a free tool built for this exact purpose. While it is highly optimized for RunCloud servers, it can be used on any WordPress backend site.
Make More Pages and Endpoints
Your website is currently fetching blog posts, but you are only limited by your imagination. You can create custom views, pages, and features using the pre-built WordPress REST APIs. Astro can generate pages for any data that WordPress outputs.
Here are a few examples of what you can build next:
- Author Pages: Fetch data from the
/wp-json/wp/v2/usersendpoint to create a directory of your blog authors and their biographies. - Category Pages: Fetch data from the
/wp-json/wp/v2/categoriesendpoint to generate dynamic landing pages that group your posts by specific topics. - Custom Post Types: If you use a plugin to create a “Portfolio” or “Testimonials” post type, you can fetch them just like regular posts and design unique Astro layouts for them.
If you know a little PHP, you can even write your own WordPress plugins to create completely custom REST API endpoints tailored to your exact business needs.
To discover everything you can fetch and build, review the official WordPress API documentation:
Final Thoughts
Building a headless architecture with WordPress and Astro offers the best of both worlds: you get the unmatched content management experience of WordPress alongside Astro’s high-performance, developer-first environment.
While you are now free from the constraints of pre-built themes, a custom stack demands a fast server environment to handle deployments, security, and consistent uptime.
RunCloud bridges this gap by turning complex server administration into a streamlined, automated workflow. Once your site is deployed, RunCloud handles the “heavy lifting” (including server-level security, SSH access, system backups, and automated continuous deployments) so you can focus entirely on your work rather than managing your OS.
If you are looking for a professional-grade way to host and manage your headless infrastructure, RunCloud provides the stability and ease of use your project deserves.
Sign up for your RunCloud account and experience painless server management today.






