Blog /
Create an Astro site on WordPress in 12 minutes (Claude Code does the typing)
You picked Astro because the output is fast, static, and crawlable. Then you hit the wall every Astro dev hits on day three.
The client wants to edit copy. They want a media library, comments, search, sitemaps, an SEO plugin, a contact form, a checkout. They already pay for those inside WordPress.
So you have two options. Reinvent that stack on a headless service (Sanity, Contentful, Strapi, plus Vercel, plus Auth0, plus Stripe), wire it with REST and CORS and a reverse proxy, and quote the client another $80 to $300 a month on top of the build fee. Or ship the site, hand it off, and watch them never edit it because wp-admin is the only editor they know.
Most Astro sites die the second way. The handoff kills them. Content goes stale. The freelancer gets blamed. The next site goes back to a page builder.
A different shape
DesignSetGo Apps inverts the diagram. You build the Astro site the way you already build Astro sites. You deploy it as a DSGo App into the client’s existing WordPress install. The Astro bundle becomes the front-end. WordPress becomes the back-end you didn’t have to write, host, or bill for.
Same domain. One deploy. Real HTML at real URLs. Editor sees wp-admin. Visitor sees Astro. Crawler sees fully-rendered pages with proper meta tags.
Built on WordPress 7.0’s Connectors API, WP AI Client, and Abilities API. Translation: the site admin sets up Anthropic or OpenAI credentials once in Settings -> Connectors. Your Astro app calls dsgo.ai.prompt(). Keys never enter your bundle. DesignSetGo never sees them; we don’t proxy inference, we don’t mark it up.
What you actually get
Every route in your Astro project becomes a real HTML page at a real URL on the WordPress domain. Search engines see fully-rendered HTML at https://yoursite.com/apps/your-slug/whatever. No iframe. No client-side rehydration trick the crawler has to solve.
Inside those pages, the @designsetgo/app-client bridge gives you typed, permissioned access to the WordPress runtime:
dsgo.posts.list()anddsgo.pages.get()for contentdsgo.user.current()anddsgo.user.can()for the logged-in visitordsgo.ai.prompt()routed through the site’s AI Connector, no API keys in your bundledsgo.commerce.products.list()against the site’s WooCommercedsgo.media.upload()straight into the WP media librarydsgo.storage.app.set()for app-scoped persistence (and.userfor per-visitor)dsgo.email.send()throughwp_mail(), so installed SMTP plugins handle deliverydsgo.abilities.invoke()to call into Yoast, WooCommerce, or any plugin that publishes Abilities
Everything is gated by what your dsgo-app.json manifest declares. Nothing is ambient. Nothing leaks.
Step 1: let Claude Code scaffold it
Open Cursor, Claude Code, Aider, or Claude Desktop in an empty folder. Tell the agent to scaffold a DesignSetGo App with the Astro starter. It will run:
npx designsetgo apps init my-astro-site --astro
cd my-astro-site
npm install
Pass --astro and the starter is an Astro project. (Without the flag, you get the single-file minimal starter, which is the right pick for one-pager widgets but not what you want here.) astro.config.mjs arrives pre-wired:
import { defineConfig } from 'astro/config';
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
const manifest = JSON.parse(
readFileSync(fileURLToPath(new URL('./dsgo-app.json', import.meta.url)), 'utf8'),
);
export default defineConfig({
site: 'https://localhost',
base: `/apps/${manifest.id}/`,
output: 'static',
trailingSlash: 'always',
build: { format: 'directory' },
vite: {
build: {
// DSGo's HTML sanitizer rejects inline scripts; force Vite to emit
// every JS module as an external asset referenced via <script src="...">.
assetsInlineLimit: 0,
},
},
});
The base matches the URL the plugin mounts at. Astro rewrites asset URLs accordingly, so you write <a href={...}> and import stylesheets without thinking about prefixes. The assetsInlineLimit: 0 line is load-bearing: without it, Vite inlines small JS modules into <script> bodies and the DSGo sanitizer rejects the bundle at install.
The starter also drops in dsgo-app.json with routes pre-declared, a Layout.astro, three example pages, and a CLAUDE.md that teaches Claude Code (and every other agent) the bridge surface and manifest schema. The agent reads that file before it writes a line. That’s the thing that makes this fast.
Step 2: write pages like any Astro site
Add pages under src/pages/. Add the matching entry to the manifest’s routes array so the plugin knows about them:
{
"routes": [
{ "path": "/", "file": "index.html", "title": "Home" },
{ "path": "/about", "file": "about/index.html", "title": "About" },
{ "path": "/pricing", "file": "pricing/index.html", "title": "Pricing" }
]
}
Astro emits one HTML file per route under dist/. The manifest’s file field points at that output. With format: 'directory', a page at src/pages/about.astro lands at dist/about/index.html.
For inter-page links, use Astro’s base-aware import.meta.env.BASE_URL:
<a href={`${import.meta.env.BASE_URL}pricing`}>Pricing</a>
Step 3: pull WordPress data into your pages
This is where it stops feeling static and starts feeling like an app.
Drop a <script> block, import the bridge client, call methods. The bridge runs in the browser at view time, so the page itself stays a pure static asset the crawler is happy with.
---
import Layout from '../layouts/Layout.astro';
---
<Layout title="Latest posts">
<h1>From the blog</h1>
<ul id="posts"></ul>
</Layout>
<script>
import { dsgo } from '@designsetgo/app-client';
await dsgo.ready;
const { items } = await dsgo.posts.list({ per_page: 5 });
const ul = document.getElementById('posts');
for (const post of items) {
const li = document.createElement('li');
const a = document.createElement('a');
a.href = post.link;
a.textContent = post.title;
li.appendChild(a);
ul.appendChild(li);
}
</script>
Declare the permission in your manifest:
"permissions": { "read": ["posts"], "write": [] }
The pattern generalizes. Current user’s name in the header? Add "user" to permissions.read and call dsgo.user.current(). Render full post HTML with WordPress block stylesheets? Add "content": { "blockStyles": ["core", "auto"] } and call dsgo.content.applyBlockStyles(post) after setting innerHTML. Invoke an LLM? Add "ai" to permissions.read plus an "ai" capability block, then dsgo.ai.prompt({...}). The site’s AI Connector handles the API call. You never touch keys.
Step 4: live-source dynamic routes
Astro’s content collections are great when you control the content. But the WordPress site is the source of truth, and you don’t want to redeploy every time the editor publishes a post.
Declare a route with a :param placeholder, point it at a built-in dataset source:
{
"path": "/posts/:slug",
"file": "post/index.html",
"dataset": { "source": "wp:posts", "id_field": "slug" }
}
Now https://yoursite.com/apps/my-astro-site/posts/hello-world/ resolves at request time. The plugin looks up the post by slug, substitutes {{title.rendered}}, {{excerpt.rendered}}, {{content.rendered}} into your single post/index.html template, and serves it. (The fields are nested because the row shape mirrors WP REST; dot-notation walks the object.) One HTML file in your bundle. Every post on the site. Built-in sources cover wp:posts, wp:pages, wp:cpt:<slug>, and wc:products. Custom resolvers register through a WP filter.
Dynamic routes are Pro, so are ai.prompt, abilities.invoke, write methods, cron, and webhooks. Free gets one active app, the read-only bridge (posts, pages, user), and HTML upload. Pro adds CLI deploy plus everything in this post. 14-day trial, then license per site.
Step 5: deploy in one command
npx designsetgo apps login --site https://yoursite.com
npx designsetgo apps deploy --build
--build runs astro build first, then the CLI zips dist/, validates the manifest against the v1 schema, and posts the bundle to your site’s REST endpoint. Same slug, same URL, idempotent updates.
Open https://yoursite.com/apps/my-astro-site/. The site is live. View source: real HTML, real <title>, real meta tags, real internal links. Crawlable. Linkable. Shareable.
The whole loop, init to deployed, runs in about 12 minutes when Claude Code or Cursor is driving. The agent reads CLAUDE.md, writes the pages, edits the manifest, runs build and deploy. You watch.
The “this is the whole site” move
Everything above lives at /apps/<slug>/. That’s the right default for adding a tool, a microsite, or a marketing surface next to an existing WP install. If you want the Astro app to be the site, set:
"mount": { "mode": "root" }
The app now serves at / directly. No /apps/ prefix. No slug. Your Astro routes own the URLs the WordPress front-end would otherwise serve. Any path WP would have 404’d on falls through to your route table. Real WP pages, posts, archives, and feeds still win; root mode adds, it doesn’t shadow. The editor keeps publishing in wp-admin. The front-end stays yours.
This is where DesignSetGo Apps stops being “host an Astro site on WordPress” and starts being “WordPress is the headless CMS for your Astro site, same domain, no rewrite layer.” Editor doesn’t notice. Visitor doesn’t notice. You get every WordPress superpower at the bridge boundary, on a front-end you actually wanted to write.
Why this beats headless
Typical headless setup: Astro on Vercel, WordPress on a different host, REST or GraphQL across the network, CORS, a separate auth story, a separate deploy pipeline, a reverse proxy to fake one domain. Each layer has a failure mode. Each layer has a bill. Client pays roughly $80 to $300 a month on top of WordPress hosting. You maintain it forever, or you don’t, and it rots.
DesignSetGo Apps collapses the diagram. Astro and WordPress live at the same origin because they are the same origin. Your bundle is served by WordPress as a static asset, under the hosting plan the client already pays for. The “API” is a same-window bridge call: no network hop, no CORS, no token exchange. Auth is whatever the visitor already has with WordPress. Deploy is one command. The bill is whatever they were already paying.
Ship one this afternoon
Pick a client site. Open Claude Code or Cursor in an empty folder. Run npx designsetgo apps init. Start the 14-day Pro trial when you’re ready to deploy. Your first Astro-on-WordPress site lands at a real URL, on the client’s existing hosting, in the time it used to take to set up the headless plumbing.
- The manifest reference for the full
dsgo-app.jsonschema - The bridge API reference for every method, error code, and permission scope
- Build a WordPress app with Claude Code in 15 minutes walks the same loop with an agent driving end to end
- Apps as Abilities covers the inverse: how the Astro app you just built can publish functions the site’s AI agent can call