Deployment and environment
This page covers how the suite is deployed on Vercel, how a build gets its identity and public origin, every environment variable the code reads, and the deployment-level rules for redirects, search-engine indexing, the landing export, the developer kit and the technical documentation.
Hosting on Vercel
The README's deployment instructions are short: a Vercel project with the Next.js preset, its root at this repository's root. There is no vercel.json on the application branches; everything the platform needs comes from next.config.ts, which is a thin wrapper over content/apps/next-config.ts:
env: the build identity and the suite origin (suiteEnv);headers(): the noindex rule, caching for/vendorand/data, each app's cache rules, and the Content-Security-Policy and security headers for workspaces, task pages and object pages (suiteHeaders; see Security and network);rewrites(): each app's rewrites and the stable address of each product page's social image;redirects(): the legacy addresses (below).
Three further details of the Vercel build (16 October 2026, dd30084 and 4c59707):
package.jsonhas avercel-buildscript, which Vercel runs in place ofbuild; it isnpm run build, so the build and itsprebuildandpostbuildhooks are unchanged.- The daily Patent capture (
capture-patent.yml) writesvercel.jsonwith{"ignoreCommand": "exit 0"}on the orphan data branchdata/exclusivity, so a push of captured data never starts a Vercel deployment. Only that branch carries the file. - On Vercel the platform can move the prerendered HTML before
postbuildruns, sopostbuild/reference-routes.mjsandpostbuild/tasks-routes.mjsskip their HTML content checks there with a logged reason; CI's build runs them in full on every push (the fast chain callscheck.yml).
.gitignore also lists .surface-runtime/, a local runtime folder an editor agent wrote into the checkout on 16 October 2026; nothing in the build reads it.
turbopack.root and outputFileTracingRoot are set to the project folder so the build never treats a parent folder's lockfile as the workspace root. outputFileTracingIncludes traces docs/technical/**/*.md into the /technical route's function, because those pages are read at request time and are never served as static files.
The deployment steps the README lists:
- Set
NEXT_PUBLIC_SUITE_ORIGINonce a domain is attached. - Public pages are indexable by default following the CEO's 16 October SEO request; set
SUITE_INDEXABLE=0on a preview that must remain unindexed. - Set
NEXT_PUBLIC_DRYBENCH_URLon the consultancy site's Vercel project to the same address, so mbio.tech/apps links here. That variable belongs to the other repository; nothing in this one reads it.
Workspace account mode, Supabase and Stripe are configured separately and stay off unless WORKSPACE_ACCESS_MODE=account is set; docs/workspace-setup.md gives the activation steps and Server and accounts describes the server side.
Build identity
Exports, provenance blocks and About › Version name the build they came from. scripts/lib/build-info.mjs resolves it once per build:
| Value | Resolved from, in order |
|---|---|
| Hash | NEXT_PUBLIC_BUILD_HASH; the first seven characters of VERCEL_GIT_COMMIT_SHA; git rev-parse --short=7 HEAD in the checkout; "dev" |
| Time | NEXT_PUBLIC_BUILD_AT; otherwise the current time in UTC, to the second |
Each candidate hash is trimmed, lower-cased and cut to seven characters, and is skipped unless the result is seven hex digits. An empty NEXT_PUBLIC_BUILD_AT counts as unset. suiteEnv in content/apps/next-config.ts calls assertProductionBuildInfo during phase-production-build, so a production build that resolves to "dev" fails with an error telling you to build from a git checkout or set one of the two variables. The development server is allowed to report "dev".
The resolved values are passed to every bundle as NEXT_PUBLIC_BUILD_HASH and NEXT_PUBLIC_BUILD_AT. src/components/suite/build-info.ts reads them (falling back to "dev" and an empty string).
Suite origin
Every absolute suite address (metadata, the sitemap, social images, export provenance) starts with the origin from suiteOrigin in content/apps/maker.ts:
NEXT_PUBLIC_SUITE_ORIGIN, when set;https://plusVERCEL_PROJECT_PRODUCTION_URL, which Vercel sets at build time to the project's production address;https://drybench.vercel.app.
parseSuiteOrigin reduces the value to scheme://host[:port] and throws unless it is an absolute https: address, or http: on localhost or 127.0.0.1. A mistyped origin therefore fails the build instead of printing broken addresses. VERCEL_PROJECT_PRODUCTION_URL is not a public variable, so suiteEnv resolves the origin at build time and passes it to client bundles as NEXT_PUBLIC_SUITE_ORIGIN. maker.ts reads each variable by its full name because Next only inlines process.env.NAME reads written out in full.
Environment variables
.env.example lists the server variables with comments. .env and .env.* files are gitignored, except .env.example. Never commit values, and never give a server secret a NEXT_PUBLIC_ prefix.
Public, inlined into client bundles
| Variable | Read in | Default and notes |
|---|---|---|
NEXT_PUBLIC_SUITE_ORIGIN |
content/apps/maker.ts, via content/apps/next-config.ts |
Vercel production address, else https://drybench.vercel.app; must be https except on localhost |
NEXT_PUBLIC_BUILD_HASH |
scripts/lib/build-info.mjs, src/components/suite/build-info.ts |
Resolved as above; required in effect when there is no git checkout and no VERCEL_GIT_COMMIT_SHA |
NEXT_PUBLIC_BUILD_AT |
scripts/lib/build-info.mjs, src/components/suite/build-info.ts |
Build time in UTC |
Build settings
| Variable | Read in | Default and notes |
|---|---|---|
SUITE_INDEXABLE |
content/apps/next-config.ts (noindexRules) |
Unset or 1: public indexing enabled. 0: every response carries X-Robots-Tag: noindex |
SUITE_KIT |
src/app/(workspace)/dev-kit/page.tsx, src/app/(workspace)/dev-kit/workspace/page.tsx; e2e/smoke/apps.spec.ts |
Unset: the dev kit answers 404. 1 builds it (SUITE_KIT=1 npm run build) |
NODE_ENV |
content/apps/next-config.ts, src/components/suite/a11y/announce.ts, src/components/suite/a11y/shortcuts/registry.ts, src/lib/handoff/links.ts, src/lib/literature/*, src/proxy.ts |
Set by Next. In development the CSP allows eval for React's tools and some development-only warnings run |
Set by the platform
| Variable | Read in | Effect |
|---|---|---|
VERCEL |
src/lib/workspace/server-config.ts, src/lib/literature/sources.ts, src/lib/literature/extraction-access.ts, src/lib/literature/search/quota.ts, scripts/postbuild/tasks-routes.mjs |
Marks a hosted deployment: disables the local development bypasses and the local literature allowances; the postbuild check looks for HTML under .vercel/output |
VERCEL_GIT_COMMIT_SHA |
scripts/lib/build-info.mjs |
Second source of the build hash |
VERCEL_PROJECT_PRODUCTION_URL |
content/apps/maker.ts |
Second source of the suite origin |
Server only, read at request time
All of these are optional. With none set, the suite runs in browser mode with the literature features that need no keys.
| Variable | Read in | Default and notes |
|---|---|---|
WORKSPACE_ACCESS_MODE |
src/lib/workspace/server-config.ts, src/lib/literature/extraction-access.ts, src/lib/literature/search/quota.ts, src/app/api/literature/search/route.ts |
browser. Only account enables accounts; credentials alone never do |
WORKSPACE_SITE_URL |
src/lib/workspace/server-config.ts, src/lib/literature/extraction-access.ts, src/lib/literature/sources.ts, src/lib/literature/search/quota.ts |
Unset. The exact https origin of the deployment (http allowed on localhost); mutation endpoints refuse other origins |
WORKSPACE_DEVELOPMENT |
src/lib/workspace/server-config.ts |
Off. 1 enables a local development namespace only when neither VERCEL nor WORKSPACE_SITE_URL is set, and only for loopback requests |
SUPABASE_URL |
src/lib/workspace/server-config.ts |
Unset |
SUPABASE_PUBLISHABLE_KEY |
src/lib/workspace/server-config.ts |
Unset |
SUPABASE_SERVICE_ROLE_KEY |
src/lib/workspace/server-config.ts |
Unset; secret |
STRIPE_SECRET_KEY |
src/lib/workspace/server-config.ts |
Unset; secret |
STRIPE_WEBHOOK_SECRET |
src/lib/workspace/server-config.ts |
Unset; secret |
STRIPE_WORKSPACE_PRICE_ID |
src/lib/workspace/server-config.ts |
Unset. The only price checkout offers |
STRIPE_WORKSPACE_LEGACY_PRICE_IDS |
src/lib/workspace/server-config.ts |
Empty. Comma-separated earlier price IDs that still grant the entitlement but are never offered |
OPENALEX_API_KEY |
src/app/api/literature/route.ts, src/lib/literature/sources.ts, src/lib/literature/search/providers.ts |
Unset; sent as a bearer token to OpenAlex when set |
ANTHROPIC_API_KEY |
src/app/api/literature/extract/route.ts, src/app/api/study/summary/route.ts, src/lib/literature/anthropic.ts, src/lib/literature/search/assistant.ts |
Unset: AI extraction, summaries and search assistance report that they are not configured |
ANTHROPIC_WORKSPACE_ID |
src/lib/literature/anthropic.ts, src/lib/literature/search/assistant.ts |
Unset; needed only when the key is not scoped to a workspace |
ANTHROPIC_LITERATURE_MODEL |
src/app/api/literature/extract/route.ts, src/app/api/study/summary/route.ts, src/lib/literature/search/assistant.ts |
EXTRACTION_MODEL in src/lib/literature/extraction.ts |
LITERATURE_AI_ENABLED |
src/lib/literature/extraction-access.ts, src/lib/literature/search/quota.ts |
Off. Hosted AI features also need 1 here and account mode |
LITERATURE_AI_DAILY_REQUESTS |
src/lib/literature/extraction-access.ts |
25; an integer from 1 to 1,000, per process |
LITERATURE_SEARCH_SHARED_QUOTA |
src/lib/literature/search/quota.ts, src/lib/literature/search/assistant.ts |
Off. Hosted OpenAlex search and AI planning stay closed until it is 1 and the Supabase URL and service key are set |
LITERATURE_OPENALEX_DAILY_REQUESTS |
src/lib/literature/search/quota.ts |
500; at most 10,000 |
LITERATURE_SEARCH_OWNER_DAILY_REQUESTS |
src/lib/literature/search/quota.ts |
100, never above the OpenAlex limit; at most 10,000 |
LITERATURE_SEARCH_ASSIST_DAILY_REQUESTS |
src/lib/literature/search/quota.ts |
25; at most 1,000 |
LITERATURE_SEARCH_ASSIST_OWNER_DAILY_REQUESTS |
src/lib/literature/search/quota.ts |
25, never above the assist limit; at most 1,000 |
A limit outside its range, or not an integer, falls back to its default.
Tests and tooling only
ALLOW_DRAFT_DATA is a builder-worktree policy exception, not an enforced
runtime isolation boundary. It must remain unset for CI and merge acceptance;
freeze drafts before merging. The independent write-once gate does not read
this switch.
| Variable | Read in | Default and notes |
|---|---|---|
ALLOW_DRAFT_DATA |
src/lib/hosts/golden.test.ts, src/lib/hosts/presets.test.ts, src/lib/seq/plasmid-data.test.ts |
Unset. Only 1 allows draft-marked Bioreactor golden runs/presets and Plasmid data versions through these tests' no-draft checks in a builder's worktree. Hash and numerical checks still run. Never set for CI or merge acceptance; freeze drafts before merging. No application-runtime effect. |
CI |
playwright.config.ts |
Set by GitHub Actions: forbidOnly, one retry, two workers, HTML report, no server reuse |
E2E_PORT |
playwright.config.ts |
3210; the account-mode server uses the next port |
E2E_BASE_URL |
playwright.config.ts |
Unset. When set, tests run against that address and no server is started |
E2E_ACCOUNT_BASE_URL |
playwright.config.ts |
http://127.0.0.1:<E2E_PORT + 1> |
PLAYWRIGHT_CHROMIUM |
playwright.config.ts, e2e/fixtures/protein-explorer/record.mjs |
/opt/pw-browsers/chromium-1194/chrome-linux/chrome, used only if it exists |
PLAYWRIGHT_NODE |
e2e/fixtures/protein-explorer/record.mjs |
@playwright/test |
HTTPS_PROXY |
e2e/fixtures/protein-explorer/record.mjs |
Passed to Chromium as its proxy when recording fixtures |
The workflows also set SCARF_ANALYTICS=false to stop a transitive dependency's install telemetry, and the snapshot commands use Node's own NODE_USE_ENV_PROXY=1 behind an HTTPS proxy (see Data pipeline).
Public indexing and preview override
The CEO's 16 October 2026 SEO request supersedes D-0026's default global
noindex behaviour. Public landing pages, methods pages, reference workflows,
Bench's empty public scaffold (/o) and Literature are indexable. Set
SUITE_INDEXABLE=0 for a preview-wide X-Robots-Tag: noindex override.
Robots permits crawling and points at the sitemap. Private object, study,
named-work, share and ordinary app-workspace metadata retain their own
noindex rules. This change does not publish local user data in static HTML.
The reference routes and social cards are generated at build time; see
Public reference workflows. No deployment or search
console submission is performed by building the code.
Legacy redirects
D-0008 moved the suite to the root of its own origin. legacyRedirects in content/redirects.ts generates permanent redirects from the registry, so the old addresses on the maker's site still work once their host is swapped:
| Old address | New address |
|---|---|
/apps |
/ |
/apps/docs, /apps/pricing, /apps/security, /apps/regulatory, /apps/changelog |
the same page at the root |
/apps/<slug> |
the product page (/protein, /molecule and so on) |
/apps/<slug>/app and /apps/<slug>/app/<page> |
the workspace and its sub-pages |
/apps/<slug>/opengraph-image |
the product page's stable social-image address |
/apps/docs/<slug> |
the app's docs page |
/tools/<slug> (Protein, Molecule, Pipeline, Cell) |
the workspace, from legacyPath in content/apps/identity.ts |
/tools/inside-a-cell/s/<step> |
/cell/app?step=<step>, from Cell's own legacyRedirects |
<slug> is the registry slug (for example protein-explorer); the destination uses the app's route segment from content/apps/routes.ts. Next passes query strings through, and the browser keeps a #s= fragment across a redirect whose target has none, so a shared link keeps its state.
The landing export for mbio.tech
mbio.tech/apps is a copy of this site's landing page, rendered by the consultancy repository from /landing.json. The route src/app/landing.json/route.ts is static (dynamic = "force-static"), sends Access-Control-Allow-Origin: *, and returns landingExport() from src/app/(marketing)/landing-data.ts, which the landing page reads too. Copy and section changes therefore reach the copy without any work here.
The export carries version: LANDING_EXPORT_VERSION, currently 7 (docs/technical/registry-and-content.md has each version's change). Version 5 (D-0112, 21 October 2026) moves the storage line from trust.storage to workspace.storage, beside Bench, where the page now shows it; sectionOrder followed the page's order of that day (D-0132 of 24 October 2026 reorders its values to hero, guided-example, trust, how-it-works, apps, ai-integration, education without a bump) and the catalogue groups run discovery, bench, then literature (Papers & patents) last. Version 4 added aiIntegration and its section-order entry, and apps.sharedTools plus catalogue copy fields (literatureHeading, literatureDescription, literatureIcon, patentDescriptor). Registered items include Patent through the shared catalogue adapter, while Literature is exported as a shared tool; the hero selection is unchanged. The complete field inventory is in Registry and content. When you add, rename or remove a field, bump the constant and tell the consultancy owner: mbio.tech keeps its last compatible snapshot until its renderer supports the new shape. Its refresh is at most hourly; a refresh alone does not add version-5 support. Version 3 previously replaced the dated Workspace screenshot fields with workspace.preview (D-0038).
The AI section is written as done (D-0134) and shows one figure, the assistant's question and the Calc tool's answer from the worked example's slots; the figure's copy is not exported (24 October 2026). Before D-0134 it described planned hosted connections for assistants such as ChatGPT and Claude, with an explicit not-yet-available caption. Its existing four-tool inventory and setup action are nested under aiIntegration.localPreview; the planned section does not advertise a live connection URL. This is a marketing contract only: no hosted service, authentication integration or deployment configuration is added, and Q-17 remains open.
The developer kit
/dev-kit and /dev-kit/workspace are the primitive gallery and a demonstration workspace. Both pages call notFound() unless SUITE_KIT is 1, so they exist only in builds made with SUITE_KIT=1 npm run build. They are never deployed, never linked, left out of the sitemap and deliberately not named in robots.txt. e2e/smoke/apps.spec.ts expects 200 or 404 according to the variable in the test environment.
Technical documentation
These pages are served at /technical with no name or password (D-0081, 13 October 2026; D-0052 had put them behind HTTP Basic authentication). src/proxy.ts matches /technical and /technical/:path* and sets the area's own security headers from src/lib/technical-docs/headers.ts: no shared caching, noindex, a Content Security Policy with no third-party hosts. The page is rendered on each request and reads only files listed in content/technical-docs.ts; any other address under /technical answers 404. No environment variable is read. TECH_DOCS_USER and TECH_DOCS_PASSWORD, if still set on a deployment, are unused and can be deleted.
The CEO’s homepage MCP marketing request of 18 October 2026 authorizes one narrow exception to D-0081’s public-link restriction: the homepage’s local-preview setup action links to /technical/mcp-server. The same destination is exported in aiIntegration.localPreview.href for the consultancy rendering. This does not open general public navigation to the technical area or change its noindex, sitemap exclusion, caching or security headers. It does not deploy an MCP endpoint: the existing server remains a local repository-run development preview and Q-17 remains open.
See Build, test and CI for how builds are checked before they reach Vercel, and the overview for the rest of this set.