Blog /
Why your custom widget should be a manifest, not a microservice
This is the post I wish someone had handed me five years ago. It would have saved me a couple hundred hours.
If you have ever built a small custom widget for a website (an interactive calculator, a configurator, a quote tool, a dashboard, a quiz, anything in that “useful but bespoke” bucket), you have probably followed this pipeline:
- Decide on a framework (React, Vue, Svelte, whatever your team likes).
npm create vite@latest.- Build the widget.
- Decide on a host (Vercel, Netlify, Cloudflare Pages).
- Set up a deploy pipeline (Git push to deploy, sometimes with GitHub Actions for builds).
- Decide on an environment-variable scheme for any secrets the widget needs.
- If the widget needs to talk to the parent WordPress site for data, set up CORS on the WordPress side, set up an authentication scheme for the cross-origin calls, write the fetch wrapper.
- Embed via iframe on the WordPress page.
- Deal with iframe-resize-on-content-change, cross-origin postMessage for any parent-child communication, and the eventual “wait, our auth doesn’t work in iframes in Safari because of cookie partitioning” surprise.
- Six months later, the widget needs an update. You context-switch back into the Vercel project, remember which env vars matter, debug the CI you set up half a year ago, ship.
The widget itself is maybe 200 lines. The infrastructure around it is everything else.
This post is the reset. A small widget is a manifest, not a microservice. The architectural unit is small; we are using a unit way too large.
The “before” diagram
Six boxes.
- The widget code itself (a Vite project).
- The hosting platform (Vercel project).
- The deploy pipeline (GitHub repo, GitHub Action, Vercel hook).
- The environment-variable management (Vercel dashboard, .env files for local dev).
- The CORS configuration on the WordPress side (a small plugin or .htaccess rules).
- The cross-origin auth layer (whatever scheme you came up with, JWTs or signed URLs or app passwords).
Each box has its own lifecycle, its own dashboard, its own update channel, its own failure modes, and its own price.
The widget code is 1% of the surface area. The infrastructure is the other 99%. The infrastructure also generates 100% of the maintenance burden, because the widget itself does not change much after launch but the infrastructure rots continuously (dependency updates, framework upgrades, Vercel UI changes, GitHub Actions versions).
The “after” diagram
One box.
- A folder with the widget’s HTML, JS, CSS, and a
dsgo-app.jsonmanifest. Deployed to a WordPress site withnpx designsetgo apps deploy.
That is the whole architecture.
No Vercel project. No GitHub Actions setup for the widget specifically (the WordPress site already has whatever it has). No environment variables (secrets live in WordPress Connectors, as covered in the secrets post). No CORS configuration (the widget is same-origin with WordPress because it lives on the WordPress site). No cross-origin auth scheme (the visitor’s WordPress cookie is already in flight). No iframe-resize dance with the parent page (the runtime handles it).
The widget is the artifact. The infrastructure is implicit in the runtime.
The maintenance cost over a year, sketched
Made-up but representative numbers from my own projects.
Widget-as-microservice version, year 1:
- 2-4 hours of initial pipeline setup.
- 1-2 hours of CORS / auth debugging on the first deploy.
- 30 minutes per quarter for dependency updates (Vite, the framework, the host’s CLI).
- 1 hour per quarter for one infrastructure surprise (Vercel changed their build syntax; GitHub Actions deprecated a node version; etc.).
- Total: roughly 6-10 hours of maintenance per year not directly related to the widget.
Widget-as-DSGo-App version, year 1:
- 5 minutes of initial setup (
npx designsetgo apps init). - 0 hours of CORS / auth debugging (the runtime handles it).
- 0 hours per quarter for hosting-side dependency updates (there is no separate hosting).
- 15 minutes per year for the occasional DesignSetGo runtime upgrade.
- Total: under 1 hour of maintenance per year not directly related to the widget.
The difference is bigger than it looks because the small infrastructure tasks have a fixed cost of attention every time you context-switch back into them. The microservice version is not just 10 hours; it is 10 hours spread across 10 different “wait, what was this project’s deploy setup again?” moments. That fragmentation is its own cost.
When the microservice shape is actually right
This is the part that has to be honest.
A widget should be a microservice when:
It does significant server-side work. Image processing, machine learning inference outside of what an MCP-style Connector can route, server-side rendering of large datasets, anything where the JavaScript bundle plus the WordPress backend cannot serve the work. If you genuinely need a server-side compute layer, the microservice is doing real work. Pay the infrastructure cost.
It serves multiple unrelated sites. A widget that is hosted by one team and embedded on dozens of customer sites (a Calendly-shaped product, a Drift-shaped product) is fundamentally a SaaS, and SaaS belongs on its own hosting infrastructure. DSGo Apps are the wrong shape for that; they assume same-origin with the WordPress host.
It needs to run when the WordPress site is down. If your widget needs to keep operating even if its host WordPress site is offline, it needs its own hosting. (For most widgets, “the host site is down” means “no one is loading the widget anyway.” This case is rarer than people think.)
It has real-time requirements that the bridge cannot meet. WebSockets to a third-party service, server-sent events for live updates, anything where the bridge’s request-response model is the wrong shape. DesignSetGo can call WebSocket APIs (declare the origin in external_origins), but if the widget is fundamentally a real-time client to a non-WordPress backend, the microservice may be the right anchor.
For most “useful but bespoke” widgets, none of these apply. The widget is a UI on top of the WordPress site’s data, takes inputs from the visitor, produces a number or a recommendation, and goes home. The microservice shape is overkill.
The mental shift
The hardest part of internalizing this is the mental shift. The architectural unit for small widgets is not a deployment. It is a manifest.
The “infrastructure” for a small widget is the WordPress site it lives on. The WordPress site already has identity, content, storage, an admin UI, a deploy pipeline (the WordPress itself), and a domain. The widget inherits all of that.
If you find yourself reaching for Vercel for a widget that takes inputs and returns numbers, you are about to spend more time on infrastructure than on the widget. Stop. Try the manifest version first.
The shorter version
The historical default for small widgets (“separate Vite project, separate Vercel project, separate CI, iframe back into WordPress”) was a 99% infrastructure / 1% widget ratio. The DesignSetGo default is the inverse: the bundle is the widget, the manifest is the everything-else, the WordPress site is the infrastructure.
This is not a “DesignSetGo is better than Vercel” claim. Vercel is a great platform for the things it is for. It is not the right shape for a 200-line widget that reads three posts from a WordPress site.
Further reading
- Vercel is the wrong place for your WordPress widget: the search-intent version of this argument.
- WordPress is the backend your vibe-coded app forgot it needed: the broader case for WordPress as the durable layer.
- DesignSetGo vs headless WordPress: the architectural comparison with the heaviest “microservice for widgets” pattern.