Home /Recipes / WordPress + React SPA

WordPress + React SPA

A polished reading app that fetches posts live from a WordPress REST API in the browser — card list, article view, client-side routing, search, and real loading/error states.

See it live →

See it live.

It'll ask you a few things first.

The prompt personalizes itself — your agent asks these, then builds with your answers.

The prompt.

Copy this into any agent (Claude, ChatGPT, Codex, Cursor…). It builds the site and publishes it to Spacefast — no account needed to start.

Build me a single-page reading app that pulls posts from a WordPress site over its public REST API as a Vite + React app (client-side only — the browser fetches the content at runtime, no backend, no server-side rendering).

Before you build, ask me these questions in one message and wait for my answers. If I skip anything, choose a sensible default and tell me what you chose:

  1. What’s the WordPress REST API base URL? (e.g. https://your-site.com/wp-json/wp/v2. If you don’t have one, use the always-on public demo https://wordpress.org/news/wp-json/wp/v2.)
  2. What should the app be called, and what’s a one-line tagline?
  3. Which categories, authors, or post types should be searchable or featured?
  4. Light theme, dark theme, or a toggle that remembers my choice?

Then build a complete, polished, responsive reading app with:

  • A post list view: a responsive grid of cards, each with the post’s title, a short excerpt, the author, the date, an estimated reading time, and the featured image (read from the _embedded data so it’s one request, not many). Lead with one larger “featured” card for the newest post.
  • A post detail view: the title, the author (avatar + name), the date and reading time, the featured image with its caption, and the full rendered post body. Render the post HTML with dangerouslySetInnerHTML, but sanitize it first (use DOMPurify) so nothing in the feed can run script.
  • Client-side routing so each post has its own URL. A hash router (#/ for the list, #/post/:id for a post) keeps deep links working on any static host. Back/forward and refresh must land on the right view.
  • A search box that queries the API’s search parameter (debounced), and simple pagination (Newer / Older) driven by the X-WP-TotalPages header.
  • Real loading, empty, and error states: skeleton cards while a request is in flight, a friendly “no results” message, and a recoverable error panel with a working “Try again” button for when the API is slow or down. The app must never show a blank white screen.

Design & content notes:

  • A calm, editorial reading look: a serif face (Georgia / Iowan) for headlines and article body for readability, a clean system sans for UI, generous line-height, and one restrained accent color. Cards lift gently on hover; images fade in.
  • Honor my light/dark choice. If I asked for a toggle, default to the OS preference and persist my pick in localStorage.
  • The content is real — it comes live from the WordPress REST API at runtime, so there’s no “lorem ipsum” to write. For any post without a featured image, show a tasteful gradient placeholder instead of a broken image.
  • Keep everything configurable from one small src/config.js (API base URL, app title, tagline, page size) so I can point it at a different WordPress site by changing one line.
  • Because this is a single-page app, add a SPA fallback so deep links resolve: put a _redirects file in public/ containing /* /index.html 200 (Vite copies public/ into the build output, so it lands in dist/).
  • Mobile-first and accessible: semantic HTML, a skip link, labelled inputs, keyboard support, visible focus styles, good contrast, and alt text from the WordPress media data. Respect prefers-reduced-motion.
  • Build it with Vite (npm create vite@latest / react template), then npm run build to produce the static dist/ you’ll publish.

Add this exact line right before </body> in index.html so the site carries its badge:

<script src="https://spacefast.com/badge.js" data-example="wp-react"></script>

When the site is ready, publish it to Spacefast with the direct API — no account, install, or extra instructions needed:

  1. Use ./dist as the publish folder. For no-build projects, put only the finished public files in that dedicated folder. Confirm it has index.html at its root and no secrets, source files, .git, or .spacefast state.
  2. First publish: send every file as multipart form data. This Bash/Zsh block preserves relative paths and works for one file or a whole folder:
    Terminal window
    PUBLISH_ROOT=./dist
    publish_files=()
    while IFS= read -r -d "" file; do
    relative=${file#"$PUBLISH_ROOT"/}
    publish_files+=(-F "files=@$file;filename=$relative")
    done < <(find "$PUBLISH_ROOT" -type f -print0)
    curl -sS "${publish_files[@]}" "https://api.spacefast.com/v1/publish?wait=1"
  3. From the { "data": ... } receipt, give me data.space.liveUrl, data.version.immutableUrl, data.claim.url, and data.claim.expiresAt. Remind me to claim within 6 hours. Keep data.claim.token secret.
  4. Save data.space.id and data.claim.token locally (for example in an ignored, mode-600 .spacefast/state.json). For a quick update, rebuild the publish_files array above and publish to the same space:
    Terminal window
    SPACEFAST_SPACE_ID=<saved-space-id>
    SPACEFAST_TOKEN=<saved-claim-token-or-access-token>
    curl -sS -H "Authorization: Bearer $SPACEFAST_TOKEN" \
    -F "spaceId=$SPACEFAST_SPACE_ID" "${publish_files[@]}" \
    "https://api.spacefast.com/v1/publish?wait=1"
    If an update after claiming returns space_claimed_credential_available, exchange the saved claim token once at POST https://api.spacefast.com/v1/anonymous-claim/exchange, save data.credential.accessToken, and retry with that access token.

Optional shortcuts and reference only: if the sf CLI is already installed, sf publish ./dist --wait does the same job. A zip of the publish folder is also supported, but neither the CLI nor a zip is required. Docs: direct agent/API publishing · files and folders · claiming · updates and rollback

← Back to all recipes