M

Architecture proposal

Scaling xCloud's deployment pipeline for the AI builder era

Lovable's shift to TanStack Start SSR introduces a new deployment type that xCloud does not yet handle. With Bolt and Replit on the roadmap, the question is not just how to support Lovable SSR — it's how to build an architecture where adding any new AI builder is trivial.

May 7, 2026 · xCloud / Lovable / Architecture

xCloud already supports static Lovable deployments end-to-end. The build pipeline runs npm install and npm run build, Nginx serves the output with proper SPA fallback, and PM2 is correctly skipped for static sites. This works well.

The challenge is what comes next. On April 20, 2026, Lovable began generating TanStack Start SSR applications for new projects — a fundamentally different deployment model that requires a running Node.js process instead of static file serving. And with Bolt and Replit planned for future integration, we need an architecture that scales beyond one-off type checks.

Industry context

Lovable's SSR transition

What changed on April 20, 2026

Lovable switched new projects from client-side React SPAs to TanStack Start, a full-stack React meta-framework built on TanStack Router. Existing projects remain as SPAs with no announced migration path.

What TanStack Start provides

Server-side rendering on every request, server functions via createServerFn() for secure backend logic, file-based type-safe routing, and built-in data fetching with TanStack Query (caching, deduplication, background refetching).

Why this matters for hosting

SSR apps need a running Node.js process — there is no index.html to serve statically. The server renders HTML per request, handles server function RPCs, and manages secrets via process.env instead of build-time VITE_* variables.

The SEO driver

Pre-SSR Lovable apps sent an empty HTML shell to crawlers. With TanStack Start, Googlebot receives fully rendered HTML with meta tags, canonical links, and structured data on first request. This was the primary motivation for the transition.

Rollout status

The transition appears gradual — some users reported new projects still lacking SSR as of April 23. This may indicate A/B testing or a phased rollout. Both output types will coexist for the foreseeable future.

Deployment landscape

Three output types from one platform

Lovable projects now produce one of three deployment archetypes depending on when the project was created and what template was used.

Static SPA (Vite + React Router)

Fully supported

All projects before April 20, 2026 — and some after

Stack React 19, Vite 7, React Router, Tailwind CSS, shadcn/ui, Supabase client SDK
Build output dist/index.html + dist/assets/*
Runtime None — static files served by Nginx
Nginx strategy try_files $uri $uri/ /index.html (SPA fallback)
Env vars VITE_* prefix, embedded at build time — rebuild required on change
Detection react-router-dom in package.json, src/App.tsx exists, dist/index.html after build

SSR App (TanStack Start + Nitro)

Not yet supported

New projects after April 20, 2026 — gradual rollout

Stack React 19, TanStack Start 1.x, TanStack Router, Nitro server engine, Vite 7
Build output .output/server/index.mjs + .output/public/*
Runtime Node.js process managed by PM2
Nginx strategy Reverse proxy to localhost:PORT
Env vars process.env.* read at runtime — restart suffices on change
Detection @tanstack/react-start in package.json, src/router.tsx exists, .output/server/index.mjs after build

Edge App (TanStack Start + Cloudflare Workers)

Cannot be supported on VPS

Niche variant — depends on project template or user prompt

Stack Same as SSR + @cloudflare/vite-plugin, wrangler.jsonc
Build output Worker bundle — no .output/server/index.mjs
Runtime Cloudflare V8 isolates (not Node.js)
Nginx strategy N/A — runs on Cloudflare edge network
Env vars Worker secrets set via Cloudflare dashboard
Detection wrangler.jsonc or wrangler.toml in project root

Current state

What works today

Site creation

Lovable site type is properly registered. Correct icon, no database provisioning, Node runtime installed.

Build execution

npm install and npm run build run correctly during deployment via the Node app deploy path.

Static file serving

Nginx config detects isStaticSite() and applies try_files $uri $uri/ /index.html — SPA routing works.

PM2 skip for static

PM2 startup is correctly skipped for static Lovable sites — no unnecessary process running.

Git integration

Git pull, deploy script execution, and file serving chain work end-to-end for static builds.

Gaps

What needs work

SSR deployment

High

New Lovable projects using TanStack Start produce .output/server/index.mjs instead of dist/index.html. The platform has no path to run these as PM2 processes with reverse proxy.

Build output detection

High

No post-build validation. If a TanStack Start project builds but produces no dist/index.html, the deployment silently fails with no guidance to the user.

Edge target rejection

Medium

Projects targeting Cloudflare Workers (wrangler.jsonc present) will build but produce no usable output. Should detect and fail with an explanation instead of deploying broken.

Scaling to new builders

Medium

Adding a new AI builder (Bolt, Replit) requires touching 14+ files across backend, frontend, scripts, and config. Type-specific checks are scattered throughout the codebase.

Web root default

Low

Current default web root is dist/. Newer Lovable projects may output to dist/client/. Need flexible web root handling per deployment type.

Architecture concern

The scaling problem

The current architecture ties site identity (who built it) to deployment behavior (how to deploy it) through scattered type checks across the codebase. Adding a single new AI builder today means touching 14 files across backend, frontend, scripts, and configuration:

1.SiteType.php — new enum case + type-checking method
2.Site.php — new is{Type}() method + add to isNodeApp() array
3.Site.php isStaticSite() — add type-specific condition
4.Site.php manager() — new match arm for service class
5.SiteProvision.php — new provisioning conditional
6.defaultDeployScript.blade.php — new @if block for build detection
7.gitPullRepository.blade.php — add type to SSR/PM2 check
8.Advanced.vue — update hardcoded type arrays (4 places)
9.SingleSiteSidebar.js — add sidebar items
10.useSiteTypeIcon.js — add to type-checking functions
11.appTypes.js — register app type definition
12.SiteMigrationController.php — add default configuration
13.reGenerateNginxConfig.blade.php — ensure correct config path
14.Language files — add translations

Every AI builder produces the same 2-3 output types (static SPA, Node SSR, or edge runtime). The deployment logic should care about what was built, not who built it.

Proposed solution

Capability-driven SiteType enum

The SiteType enum already declares capabilities like filesBackupSupported() and databaseBackupSupported(). We extend this pattern so that every downstream system — deploy scripts, Nginx config, frontend UI — reads capabilities from the enum instead of checking type identity.

Method Purpose Replaces
isAiBuilder() Identifies AI app builder platforms (Lovable, Bolt, Replit) Scattered isLovable() || isBolt() checks across the codebase
supportsSSR() Whether this type can run as a Node.js SSR application isNodejs() || isLovable() checks and growing if-chains
defaultDeployMode() Default mode when site is created (static_spa or node_ssr) Hardcoded if-chains in isStaticSite()
defaultWebRoot() Default web root per type (dist/client, dist, null) Hardcoded web_root assignments in SiteMigrationController
defaultStartCommand() Default PM2 start command for SSR mode Hardcoded start_command logic in Vue components
unsupportedTargetSignals() Files that indicate an unsupported deployment target Nothing — this is new capability for detecting CF Workers, Vercel, Netlify targets

Adding Bolt today: 14 files

New enum case, new model methods, update type arrays in 4 Vue files, new deploy script block, new Nginx conditional, new sidebar section, migration defaults, language files.

Adding Bolt after: 4 files

New enum case with match arms in SiteType.php, one line in Site manager(), app type definition in frontend, translations. All deploy logic inherits from capabilities.

Deploy safety

Post-build detection

Instead of assuming what the build will produce based on site type, validate the actual output after npm run build completes. This catches edge targets, misconfigurations, and framework changes automatically.

wrangler.jsonc or wrangler.toml exists Reject — targets Cloudflare Workers, not deployable on VPS
.output/server/index.mjs exists + site is SSR Deploy as Node SSR — PM2 + Nginx reverse proxy
.output/server/index.mjs exists + site is static Warn — SSR output detected but site configured as static
dist/client/index.html exists Deploy as static SPA — Nginx with SPA fallback
dist/index.html exists Deploy as static SPA — Nginx with SPA fallback
None of the above Reject — no recognized build output

Implementation

Incremental migration — five phases

No big-bang rewrite. Each phase is a standalone PR that can be reviewed, tested, and shipped independently.

1

Add capability methods to SiteType enum

New methods only, no behavior change. Follows existing pattern of filesBackupSupported() and databaseBackupSupported().

Risk: None

2

Refactor Site model to use capabilities

Replace isStaticSite() logic and scattered type checks with $this->type->supportsSSR() calls. Same behavior, cleaner code.

Risk: Low

3

Add SSR deployment support for Lovable

Gate PM2 startup and Nginx reverse proxy on supportsSSR() + meta->is_ssr_app. Add post-build output detection.

Risk: Medium

4

Add unsupported target detection

Detect wrangler.jsonc, vercel.json, netlify.toml in deploy script. Fail with actionable guidance instead of silent broken deploy.

Risk: Low

5

Expose capabilities to frontend

Pass capabilities via SiteResource. Replace hardcoded type arrays in Vue with capability-driven checks.

Risk: Medium

References

Sources

Recommendation

Summary

Static Lovable deployments work. The existing pipeline handles them correctly — build, serve, SPA fallback, no PM2. This is the majority of Lovable projects today and will remain so for a while.

SSR support is the immediate gap. New Lovable projects using TanStack Start need a Node.js process, reverse proxy, and runtime environment variables. This is the most impactful feature to add next.

The capability-driven architecture is the long-term investment. Five incremental PRs transform "adding a new AI builder" from a 14-file integration project into a 4-file configuration change. This pays for itself the moment Bolt or Replit are added.

Post-build detection prevents silent failures. Validating actual build output instead of assuming structure catches edge targets, framework changes, and misconfiguration — regardless of which AI builder produced the project.

Back to writing Previous: BYOS deployment report