A Form on `site.example.com` can now send a Resource request to `api.example.com`. Cloudflare fetches use public routing, while requests back into the same app are stopped even when they arrive through a different hostname. ## Checklist - [x] Enable public `fetch()` routing in the self-hosted Cloudflare Worker and Pages templates. - [x] Narrow Cloudflare Form Resource hostname checks to site hostnames, including published aliases; keep the Node transport policy intact. - [x] Explain the routing and recursion boundary in code comments. - [x] Add focused regression tests and verify they fail before the fix and pass afterward. - [x] Reject Cloudflare Resource requests that re-enter the app through an alias, and verify both Worker and Pages paths. - [x] Run relevant package tests, typechecks, lint, and fixture builds. - [x] Review the authoritative `docs/` content. No user guidance currently describes this hostname policy, so no docs change is needed. The `global_fetch_strictly_public` flag affects all Cloudflare `fetch()` calls in the generated app. It routes same-zone requests through the public front door. The known site hostnames are blocked before fetch; a reserved request marker carrying the originating project ID stops direct reentry through any additional self-hosted alias while allowing other projects. The marker is set on every redirect hop. Verification: SDK suite (914 tests), CLI prebuild suite (143 tests), SDK and CLI typechecks, published types, lint, package boundaries, formatting, full fixture pipeline, and both Cloudflare fixture typechecks/builds passed. The new marker test failed before the fix and passed afterward. Local Pages returned 508 for a marked request and 200 for an ordinary request. Local Worker returned 508 for a marked request; an ordinary local request hit a fixture-only `assets/process` module error. The optional local fixture-size check lacked the unrelated React Router Netlify built client artifact; the prior PR CI fixture-size check passed. Staging test: moved from #6621 to `frm.staging`. The Builder deployed at https://frm.staging.webstudio.is/ and the Cloudflare template artifact build and validation passed. - [x] Publish the test site from staging and verify a Resource call to a sibling API hostname, plus recursive-call protection. Live staging verification: Published `ee-trpc-test-ivan.wstd.work` from Builder commit `35d22fc8a`. A temporary Resource to `api.wstd.work` produced that host's HTTP 401 response in published page data, confirming the request reached the sibling host. A marked request initially returned 200 because the SaaS publish Worker uses a separate entrypoint. The matching guard is in webstudio-is/webstudio-saas#673; deploying that branch made the marked request return 508 while an ordinary request returned 200. The temporary Resource and binding were removed and the clean build was republished with the SaaS fix. ## Resource response limit follow-up - [x] Enforce 1 MiB (1,048,576 decoded bytes) for all HTTP Resource responses, including page Resources, dependencies, Form actions, and cache hits; reject before cache writes. - [x] Cancel oversized responses and report `RESPONSE_TOO_LARGE` without retrying. Show: “Resource response exceeds 1 MiB. Request fewer fields or use pagination.” - [x] Verify exact boundaries, streamed overflow, compressed responses, cache behavior, and Form/loader error propagation with regression tests. - [x] Update Resource and Form documentation to explain the limit and recovery. - [x] Monitor CI after pushing the verified implementation. All checks passed for `f59b770d45112581514eaa8bc42f57b7f27d290b`, including all ten browser E2E jobs, visual regression, Linux/Windows fixtures, and staging builds/deployments. Verified locally: all 926 SDK tests, SDK typecheck and published types, lint, package boundaries, Form browser boundary, formatting, full fixture pipeline, and Cloudflare React Router fixture typecheck/build passed. All 12 new tests and the updated transport test failed against the previous implementation; disabling only the retry guard also reproduced a duplicate attempt. A local Cloudflare runtime test with gzip accepted exactly 1 MiB of decoded data and rejected one byte over. The full fixture sync pulled an unrelated saved-project text change; the original fixture source was restored and regenerated, leaving no fixture diff. The response limit is enforced before JSON parsing and before Resource cache writes, and checked again when loading cache hits. Oversized responses return `RESPONSE_TOO_LARGE` with `retryable: false`; the graph honors that flag. System and Email Resources retain their own limits. The implementation adds 59 net non-test code lines plus 5 documentation lines, below the 70–100 estimate. Browser E2E passed in CI. Agent evaluations were not run; they require separate permission and were not needed for this transport change. ## Simplification follow-up - [x] Reuse Content Engine's bounded byte reader for Resource bodies and Form uploads; remove duplicate accumulation and cancellation loops (estimate: about 40 fewer production lines). - [x] Preserve response limits, request limits, cancellation, error messages, and retry behavior; run existing SDK and Content Engine coverage plus package/build checks. - [x] Review documentation impact; behavior and approved copy should remain unchanged. - [x] Push the simplification and verify CI. All checks passed for `755ec4210a7858fe298ad4d3a725a0167fb5bb26`, including all ten browser E2E jobs and staging deployment checks. The simplification removes 37 net non-test lines, reducing the response-limit implementation from 59 added lines to 22. Content Engine now exposes its existing byte-stream module directly and accepts a caller-provided limit error. Resource bodies and Form uploads reuse that reader. Native Response construction also replaces redundant status/header copying. All existing tests remain. Verification: 926 SDK tests and 655 Content Engine tests passed, along with both packages' typechecks/builds, published types, lint, package boundaries, Form browser boundary, formatting, the full fixture pipeline, and a Cloudflare fixture build. Local Cloudflare runtime checks passed for gzip at/over the limit and empty 204/205/304 responses. The generated fixture vendor change is limited to the shared reader's optional error argument. Documentation and approved copy remain accurate and unchanged. ## Response handling cleanup - [x] Share bounded response reconstruction between protected fetches and the cache; remove stale encoding/length headers after decoding (estimate: 5–10 fewer non-test lines). - [x] Reproduce the protected-response header bug, then verify the fix, limits, empty responses, and existing SDK coverage. - [x] Review documentation and comments for accurate scope; preserve approved user-facing copy. - [x] Run relevant checks, regenerate fixtures, push, and monitor CI. The shared response reader fixes stale compression and length headers in protected fetches and removes four net production lines (slightly below the 5–10 estimate). All 927 SDK tests, types, published types, lint, package boundaries, and Form browser boundary passed. The new regression test failed on the old code and passed after the fix. Cloudflare runtime checks passed for decoded bodies and empty 204/205/304 responses. The existing Resource/Form documentation remains accurate; approved copy is unchanged. The reentry marker now carries the originating project ID. A different project can serve the request; the same project rejects direct reentry through any hostname. This is cooperative loop detection, not a sandbox for arbitrary code. - [x] Reproduce and fix overflow and redirect hangs when a response has an unread clone; preserve stream cancellation and error handling for all shared-reader callers. The clone regressions failed before the fixes and passed afterward. Cancellation still starts immediately and releases the reader lock, but no longer waits for an unread tee branch. Both overflow rejection and redirect following are covered. All 929 SDK tests and 655 Content Engine tests passed, along with both typechecks and lint. A local Cloudflare runtime also rejected a cloned oversized response promptly. Combined with response reconstruction reuse, this cleanup removes five net production lines. Fixture regeneration changes only the bundled cancellation logic. Final verification for `7761f67d6d1e0a853a6ef239f482ebaf9d70c1ae`: all CI checks passed, including all ten browser E2E shards, visual regression, package/Builder/CLI tests, size checks, Linux and Windows fixtures, and staging deployment workflows. Full fixture regeneration and the Cloudflare fixture build also passed locally. No agent evaluations were run; they require separate permission and were not needed for these fixes. ## Scope reentry protection to the project - [x] Replace the global marker with the originating project ID and compare it with generated project metadata before handling requests, including cached requests. - [x] Cover unrelated sites, same-project aliases, and redirects with regression tests; demonstrate failure before the fix. - [x] Review documentation and clarify that this is cooperative loop detection, not a sandbox for untrusted code. - [x] Regenerate fixtures, run relevant checks, push, and monitor CI. Estimate: 20–30 implementation lines across both repositories, plus generated metadata and tests. Implementation: reuse the project ID already exported by the generated home-page module; no new metadata file or package. The generated Cloudflare Resource adapter passes the same ID to the SDK factory, which now requires it. Guards compare that ID before rendering or cache lookup. SDK and generated-adapter regression tests cover another project returning 200 and an unknown same-project alias returning 508. Hosted Worker tests verify the guard runs before the cache and permits another project's cached response. The original guard and a blanket-guard mutation fail the regressions. A local Cloudflare runtime reproduced the expected 200/508 behavior. No user-facing copy or documentation changes are needed; code comments state the cooperative boundary. The fix adds 19 implementation lines across both repos, below the 20–30 estimate, plus tests/configuration and regenerated fixtures. Existing deployments require republishing with the updated sender and guard to use project-specific markers. Final project-specific reentry verification: 930 SDK tests, 143 CLI prebuild tests, and 41 hosted-template tests passed. SDK/CLI types, published SDK types, lint, package boundaries, full fixture regeneration, both Cloudflare fixture builds, and the Pages function bundle passed. The actual Pages function returned 200 for another project's marker and 508 for its own marker through an unknown alias in a local Cloudflare runtime. All CI checks passed for Builder `433f9a359d469ca3669e383c4516c3e805ea22a1`, including all ten E2E shards and staging workflows; companion SaaS `3e28e7e33813dd968f33caac35dbbba52a3847d3` checks and preview deployment also passed. Agent evaluations were not run because they require separate approval; no evaluation changes were needed.
Webstudio CLI
The Webstudio CLI helps you to link, sync and build projects from Webstudio Cloud. This README will guide you through the process of setting up a Webstudio project on your local machine and continually sync it with the cloud.
Prerequisites: Installing Node.js
You need Node.js to use the Webstudio CLI. If Node.js is already installed in your system, you can skip ahead to the section on installing the Webstudio CLI.
To install Node.js using NVM, first install NVM by running:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash
Once NVM is installed, you can install Node.js version 22 by running:
nvm install 22
Verify your Node.js installation by checking its version:
node --version
Installing the Webstudio CLI
To get started with the Webstudio CLI:
-
Download and install the CLI using the following command:
npm install -g webstudio@latest -
Confirm the installation by checking the CLI version:
webstudio --version -
To keep your CLI updated, use the same command used for installation whenever a new release is available.
Initiating a Webstudio Project
Now, you can run a Webstudio project on your local machine using this command:
webstudio
This will initiate the flow to connect your Webstudio Cloud project and build it in your local computer. The default flow will guide you through the steps. You can also perform all the operations individually using independent commands.
Commands
Here is the list of independent commands:
- version
- help
- link
- sync
- build
link
The link command stores access to a Webstudio Cloud project. MCP clients use that connection to read and edit the latest Builder build directly; the project does not need to be published or synchronized first.
Shell-capable agents can call MCP tools directly as CLI subcommands immediately after linking. This does not require MCP client configuration or a client restart:
webstudio meta.index
webstudio components.search '{"brief":"button"}'
webstudio insert-fragment --input-file .temp/insert-fragment.json --dry-run
.temp/insert-fragment.json:
{
"parentInstanceId": "<parent-id>",
"fragment": "<section />"
}
The explicit equivalent is webstudio mcp single-op-call <tool> '<json>'.
Use webstudio connect <client> only when you want persistent native MCP tools
inside Claude Code, Codex, Cursor, or VS Code. A persistent client may need to
reconnect after its configuration or cached tool catalog changes.
See the Webstudio MCP documentation for setup, discovery, editing, verification, and safety guidance.
You can link a project from Webstudio Cloud with the following command:
webstudio link
This command will prompt you to paste a link which you can create using the Share option in your project.
Make sure to provide Build access when generating the link in Webstudio Cloud.
sync
Once the project is linked, use the sync command to sync it with the cloud:
webstudio sync
Sync downloads the current saved Builder project and its asset files, then prepares the local content index. Publishing is not required.
Use the export with webstudio build or webstudio import --to "<target-share-link>". Sync is not required for MCP editing.
build
Now, you can build your project with the build command:
webstudio build
During this phase, the CLI establishes the necessary routes and pages, scaffolding the entire application using the default Remix template. Additionally, all assets, such as images and fonts are downloaded to the assets folder inside the public directory.
Once the project is scaffolded, you can run npm install and then npm run dev to run your app in development mode.
If you want to build a production version of the app, you can run npm run build.
Deployment
Vercel
Once you've built the project locally, you can use the Vercel CLI to deploy your project directly to Vercel:
vercel deploy
Follow the instructions here to install the vercel CLI. We plan to add more deployment targets in future.
Netlify
If you want to deploy to netlify, you can use Netlify CLI to deploy your project directly to Netlify:
netlify deploy
You can configure the project to support netlify serverless/edge-functions respectively, as deployment target at the time of initially setting up your project. Please check the initiating-a-webstudio-project section.
You can manually change it using the build command. For serverless functions:
webstudio build --template netlify
Important Notes
If you use vercel build before vercel deploy, make sure to clean your app folder in the project afterward.
Vercel injects a few files to support and deploy Remix using their CLI, but these files are not necessary for your project when you use it locally.