All skills
wix avatar

/wix-vibe-headless

@1563591 official
by Wix.comwix/skills33 stars
33

Client-only, dependency-free REST scaffolds for connecting an already-built front end (a vibe-coded app, an HTML/JSX/Vite project, a design-tool export) to a live Wix site over the site's public WIX_CLIENT_ID — the browser talks to Wix directly, no SDK, no backend, no build step. One skill covering every Wix business solution: Stores/eCommerce storefront (products, cart, checkout), Bookings (services, slots, appointments), Blog (posts, categories, tags), Events & Tickets (browse, RSVP, ticketing), Portfolio (collections, projects, galleries), Restaurants (menu, online ordering, reservations), Forms (any visitor-fillable form — contact/enquiry, signup, waitlist, application, survey, quote request; schema-driven render + submit), CMS / Wix Data (list, detail, filter, CRUD), Pricing Plans (memberships, subscriptions, checkout), and Members (custom login — email+password, Google/Facebook, and custom SSO — plus account areas and member-gated content). Each vertical ships a copy-as-is REST layer plus wiring instructions. Read-only over the owner's content — never provisions, never mocks data. Triggers: connect my Wix store/shop, build a storefront over Wix, add a cart and checkout, connect Wix Bookings, take appointments/reservations, show my Wix blog, list my Wix events, sell tickets, take RSVPs, build a portfolio from Wix Portfolio, show my restaurant menu / order online / book a table, display my Wix CMS collection, wire a contact form to Wix, add a contact/enquiry form, build a signup or application form, take survey responses, sell membership/subscription plans, add member login / sign up, let members log in with Google or Facebook, custom login page, account / profile page, gate content behind login, sign in with SSO/Okta, 'here is my WIX_CLIENT_ID', connect this app to my Wix site over REST. Use this for CLIENT-ONLY REST integration over an existing site; use `wix-headless` instead for SDK + Wix CLI builds, hosting, and one-prompt new-site creation.

Use this Skill: https://skilld.dev/gh/wix/skills/wix-vibe-headless

This session only. Nothing lands on disk.

referencesblogINSTRUCTIONS.md

≈4.3k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Wix Blog — ready-made client

The blog reader is shipped as real files, not snippets to regenerate. It's a complete feed + post detail + category/tag landing pages, styled with your app's design tokens (base44's src/index.css — the shadcn palette the design phase already set). Copy it into the app and wire the routes — you generate almost none of the reader code (post paging, slug routing, the category/tag id→label resolution, the plain-text body split all ship and are correct).

Talks to Wix directly over the public WIX_CLIENT_ID (anonymous visitor tokens). The reader is read-only and visitor-facing — it never creates, edits, or moderates content. Never mock posts; never hand-build a post/category/tag URL — route by slug through the shipped helpers.

Prerequisites

  • The site's Wix Blog is the read target. It's installed and seeded separately (see Seeding below), in parallel with this build — so it may be empty at build time; the client renders the shipped empty state until posts land. Draft/unpublished posts are never returned.
  • The public headless WIX_CLIENT_ID from your prompt (visitor-facing, safe to hardcode/commit).

STEP 1 — The client is already in src/

The install step (base44.md STEP 1) deployed the whole blog UI client + REST scaffolds into src/ (imports use the @/ alias → src/). Here's every file and what it is — this is your map, so you don't need to open them:

file what it is
context/TaxonomyContext.jsx useTaxonomy() provider: categories + tags fetched once, exposed as catById / tagById id→object maps
hooks/usePostDetail.js post-detail data — load by slug, not-found state, resolved category/tag chips, body paragraphs
components/PostCard.jsx, PostGrid.jsx post listing UI (grid + card, with cover image + empty state)
components/PostChips.jsx category/tag chips for a post (resolves ids via the taxonomy, routes by slug)
components/WixManageBanner.jsx preview-only manage banner — drop it into your Layout (STEP 3)
pages/Blog.jsx the feed route (/blog) — lists posts, paginates, empty state
pages/PostDetail.jsx the post route (/blog/:slug)
pages/CategoryPage.jsx, TagPage.jsx the taxonomy landing routes (/blog/category/:slug, /blog/tag/:slug)
rest/wix-config.js the two ids, written by the install step
rest/wix-client.js + rest/wix-blog.js REST transport + blog helpers (posts/categories/tags)

They're already in place — go straight to theming + wiring, nothing to verify first. Don't read_file the shipped page/component/hook source to inspect it — the table above says what each is and every field shape you need is in the snippets below. Read a shipped file's source only on a real fallback — a runtime error, or a field the snippets don't cover (see "Fallback only" at the end). (Files missing? the install's deploy result lists what it wrote; re-run install, or copy references/blog/app/ → src/.)

Their Link/useParams imports come from @/lib/nav, so the same sources run on either template. That adapter is installed at src/lib/nav.js with the rest of the deployed tree — use the file that is there, and on TanStack check it first (which template?). Import from @/lib/nav in the pages you write too, and they stay portable the same way.

STEP 2 — Theme

Use the existing Base44 theme in src/index.css so your pages and the shipped components share the same colors and typography.

STEP 3 — Wire routes + provider (surgical find_replace on src/App.jsx, never a rewrite)

The template decides this step, and src/routes/__root.jsx is the question to ask first. Present → TanStack Start, which mounts these pages as route files at the end of this step; absent → React Router, which the wiring below is written for. Ask in that order: an src/App.jsx can exist on a TanStack app because an agent created one, and __root.jsx is never there by mistake. The installed src/lib/nav.js defaults to the React Router adapter, so on TanStack swap it: both patterns.

Import Link, useParams and friends from @/lib/nav in the pages you write too — same names as the router exports, and nothing you write is pinned to one template.

No file reads needed to wire this. Every shipped page and WixManageBanner is a default export that takes no props — wire them exactly as the snippet shows; nothing in those files needs looking up. App.jsx carries required platform auth scaffolding (AuthProvider/useAuth) — edit it in, don't replace it.

  • Wrap the routed tree in <TaxonomyProvider> (from @/context/TaxonomyContext) so categories/tags are fetched once and every card/chip reuses the shared map (never re-query per post).
  • Put your header + footer in a Layout that renders <Outlet/> between them, and nest every route under one pathless <Route element={<Layout/>}>. Your brand chrome then wraps every page — including the shipped Blog / PostDetail / category / tag pages — so you never edit the shipped pages to add a header/footer (they render inside <Outlet/> as-is).
  • Pin the top chrome as one fixed block. Put <WixManageBanner/> (shipped, preview-only) above your <Header/> inside a single position:fixed top region — the header itself is plain in-flow markup, the region owns the fixing — so banner + header ride together (no scroll drift/gap). Pad the content by the region's measured height so it clears the chrome and self-corrects when the banner is dismissed.
  • Routes under the Layout: /blog → Blog, /blog/:slug → PostDetail, /blog/category/:slug → CategoryPage, /blog/tag/:slug → TagPage (all shipped, as-is). You add / → your own Home page.
import { useRef, useState, useEffect } from "react";
import { Routes, Route, Outlet } from "react-router-dom";
import { TaxonomyProvider } from "@/context/TaxonomyContext";
import WixManageBanner from "@/components/WixManageBanner";   // shipped, preview-only · default export, no props
import Blog from "@/pages/Blog";                       // shipped · default export, no props
import PostDetail from "@/pages/PostDetail";           // shipped · default export, no props
import CategoryPage from "@/pages/CategoryPage";       // shipped · default export, no props
import TagPage from "@/pages/TagPage";                 // shipped · default export, no props
import Home from "@/pages/Home";       // YOU build
import Header from "@/components/Header";   // YOU build — plain in-flow markup, NOT position:fixed
import Footer from "@/components/Footer";   // YOU build

function Layout() {
  const topRef = useRef(null);
  const [offset, setOffset] = useState(0);
  useEffect(() => {                                  // measure the fixed region → pad content below it
    const ro = new ResizeObserver(() => setOffset(topRef.current?.offsetHeight ?? 0));
    if (topRef.current) ro.observe(topRef.current);
    return () => ro.disconnect();
  }, []);
  return (<>
    <div ref={topRef} style={{ position: "fixed", top: 0, left: 0, right: 0, zIndex: 50 }}>
      <WixManageBanner />                    {/* null on the published site / when dismissed */}
      <Header />                             {/* your brand header, in-flow inside this fixed block */}
    </div>
    <div style={{ paddingTop: offset }}>     {/* clears the chrome; shrinks when the banner is dismissed */}
      <Outlet />                             {/* shipped Blog / PostDetail / Category / Tag render here, untouched */}
      <Footer />
    </div>
  </>);
}

<TaxonomyProvider>
  <Routes>
    <Route element={<Layout />}>                                    {/* chrome wraps all */}
      <Route path="/" element={<Home />} />                         {/* yours */}
      <Route path="/blog" element={<Blog />} />                     {/* shipped, as-is */}
      <Route path="/blog/:slug" element={<PostDetail />} />         {/* shipped, as-is */}
      <Route path="/blog/category/:slug" element={<CategoryPage />} /> {/* shipped, as-is */}
      <Route path="/blog/tag/:slug" element={<TagPage />} />        {/* shipped, as-is */}
    </Route>
  </Routes>
</TaxonomyProvider>

TanStack Start template — the same pages, mounted as files

Chrome (header, footer, the fixed banner region described above) goes in src/routes/__root.jsx around its <Outlet/>, and any provider this vertical asks for wraps that <Outlet/> once. Each route is a two-line file; shipped pages stay in src/pages/ untouched.

route file component
/ src/routes/index.jsx Home
/blog src/routes/blog.index.jsx Blog
/blog/:slug src/routes/blog.$slug.jsx PostDetail
/blog/category/:slug src/routes/blog.category.$slug.jsx CategoryPage
/blog/tag/:slug src/routes/blog.tag.$slug.jsx TagPage
// src/routes/blog.index.jsx
import { createFileRoute } from "@tanstack/react-router";
import Blog from "@/pages/Blog";

export const Route = createFileRoute("/blog")({ component: Blog });

Path params are $name in both the filename and the route path; useParams() from @/lib/nav reads them unchanged. Full pattern, including ssr: false for per-user routes: ../_shared/routing.md.

What you build (not shipped)

The home / landing page, the Header and a Footer — the two you drop into the Layout (STEP 3) so they wrap every route — plus the overall brand story, styled with the same base44 tokens/classes. Compose the shipped pieces — a "latest posts" strip is just queryPosts + the shipped PostGrid; a category nav is useTaxonomy() + links to /blog/category/:slug:

import { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import { queryPosts } from "@/rest/wix-blog";
import { useTaxonomy } from "@/context/TaxonomyContext";
import PostGrid from "@/components/PostGrid";

// Responsive header: choose ONE branch with a state flag (never a Tailwind `hidden md:*` toggle —
// these navs are inline-styled, and an inline `display` beats a Tailwind class, so `hidden` never
// applies and BOTH branches render). One branch = one nav.
export function Header() {
  const [mobile, setMobile] = useState(() => window.innerWidth < 768);
  useEffect(() => {
    const onResize = () => setMobile(window.innerWidth < 768);
    window.addEventListener("resize", onResize);            // keep it reactive to viewport changes
    return () => window.removeEventListener("resize", onResize);
  }, []);
  return (
    <nav style={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
      {/* brand/logo */}
      {mobile
        ? <YourMenu />                                       // your hamburger + links here
        : <div style={{ display: "flex", gap: 24 }}><Link to="/">Home</Link><Link to="/blog">Blog</Link></div>}
    </nav>
  );
}
export function Latest() {                                   // on your home page
  const [posts, setPosts] = useState([]);
  // NB: queryPosts returns { posts, nextCursor } — destructure the array.
  useEffect(() => { queryPosts({ limit: 6 }).then(({ posts }) => setPosts(posts)); }, []);
  return <PostGrid posts={posts} empty="Posts coming soon." />;
}
export function CategoryNav() {                              // a menu of the blog's categories
  const { categories } = useTaxonomy();                     // fetched once by the provider
  return categories.filter((c) => c.postCount > 0)          // hide empty categories if you want
    .map((c) => <Link key={c.id} to={`/blog/category/${c.slug}`}>{c.label}</Link>);
}

Everything reads base44's design tokens (index.css), so your home/nav match the shipped pages automatically.

Editing a component and the change doesn't show? It's the preview, not your code. The dev preview can serve a stale module after a write. Before diagnosing a visual bug you just "fixed", do a fresh full navigate/reload of the preview and re-check — don't keep rewriting correct code against a stale render.

Using the client from your own UI

// Every list helper returns an OBJECT, not a bare array — destructure first, or `.map` throws:
const { posts, nextCursor } = await queryPosts({ limit: 20 });          // pass nextCursor back as `cursor`
const { categories, total } = await queryCategories();                  // display c.label · count c.postCount
const { tags } = await queryTags();                                     // display t.label · count t.publishedPostCount
const { posts: inCategory } = await queryPostsByCategory(categoryId, { limit: 20 });
const { posts: withTag }    = await queryPostsByTag(tagId, { limit: 20 });

// Single-item lookups fail soft (null on miss) — show a not-found state, never invent an item:
const post = await getPostBySlug(slug);         // null → 404 state
const cat  = await getCategoryBySlug(slug);
const tag  = await getTagBySlug(slug);

// Cover image: post cover and category cover are DIFFERENT paths.
const postCover = post.media?.wixMedia?.image?.url;   // post/card cover (ready-to-use https)
const catCover  = cat.coverImage?.url;                // category landing cover
// No post cover? fall back to the first richContent IMAGE node, or a text-only card — never a stock image.

// Body: contentText is plain text (split on "\n" for paragraphs — the shipped hook does this).
// richContent is a Ricos document for a faithful render (images/embeds) — see "Extending".

No author byline — the reader helpers don't expose an author (post.author is undefined). Omit the byline, or resolve it via the members vertical. No engagement UI — comments/likes/views aren't exposed; leave them out.

Extending the client

Building something beyond the shipped pages (faithful richContent, full-text search, related posts, metrics, members-only posts)? Extend with wixApiRequest, but look up the exact endpoint/method/body in the official Wix API reference first — never guess. Each helper in wix-blog.js links its reference page inline.

Fallback only — when you hit an error or need something not shown here: read the relevant shipped file under src/, or look it up via the documentation skill available in your environment.

Hard rules

  • Header/footer live in a Layout around <Outlet/> (STEP 3) — never edit the shipped Blog/PostDetail/category/tag pages to add chrome.
  • The Layout's fixed top region owns positioning: <WixManageBanner/> above <Header/>; your Header is plain in-flow markup (not position:fixed).
  • Route by slug through the shipped helpers — never hand-build a post/category/tag URL. Display categories/tags by .label (not .name).
  • Render live Wix data or the shipped empty state — never mock posts, authors, comments, likes, or view counts; never use a stock/placeholder cover image.

Point the user to their dashboard

Provide deep links so the owner can edit content (substitute the site's metaSiteId):

  • Posts — https://manage.wix.com/dashboard/{metaSiteId}/blog/posts (write, edit, publish; only published posts appear in the app)
  • Categories — https://manage.wix.com/dashboard/{metaSiteId}/blog/categories

Seeding

Seed the blog per seed/SEED.md (the build-time module that creates posts/categories over the connector) — separate from this client build; run in parallel.

Verify (before declaring done)

  • Client files copied into src/; WIX_CLIENT_ID set (not the placeholder).
  • Opened the vertical's data route(s) (not just the home page) — /blog and a post detail page (plus a category/tag page) — and confirmed the shipped components render themed (surface, text, brand) with images.
  • Layout (fixed <WixManageBanner/> + <Header/> region, then <Outlet/> + Footer) wraps all routes; shipped Blog/PostDetail/CategoryPage/TagPage untouched; content clears the fixed chrome; <TaxonomyProvider> wraps the tree.
  • Feed lists live published posts (newest first) and paginates via nextCursor; post detail loads by slug; a bad slug shows the not-found state (no invented post).
  • Category/tag pages list the right posts; chips display .label and route by .slug; taxonomy fetched once (no re-query per card).
  • Cover images come from post.media.wixMedia.image.url (category cover from category.coverImage.url) — no stock placeholders; no author byline / engagement UI invented.
  • Empty catalog shows the shipped empty state; no mock posts anywhere.

Source: SKILL.md on GitHub

1 warning6d3 checks · Risk SAFE
  • Gen Agent Trust Hub6d

    The skill is safe and provides a comprehensive integration framework for Wix Headless services. It uses platform-secured connectors for authentication and includes automated scaffolding utilities to assist with project setup.

  • Socket6d

    No alerts

  • Snyk6d

    Risk: MEDIUM · 1 issue

Signed by skilld at 1563591. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 42 minutes ago.

Activeupdated 4 weeks ago

README badge

README badge for wix/skills/wix-vibe-headless