Frontend Deployment

The app can be deployed to AWS and Azure as a static Observable Framework frontend. This is intentionally separate from cloud backend setup: public website resources serve dist/, while file/document artifacts remain in the local object directory, private S3 buckets, or private Azure Blob containers behind the coordinating API.

Use this page for both AWS and Azure frontend deployment. Provider-specific approval notes are kept here so AWS and Azure remain symmetrical in the documentation; the old Azure appendix is retained only as a historical first-deployment record.

Deployment Sequence

Use this order when bringing up a new environment:

  1. Run locally with npm run dev:local.
  2. Confirm the local API writes metadata to .wm-data/workspace.sqlite and file bytes to .wm-data/objects/.
  3. Create application accounts for clients, businesses, departments, firms, or personal workspaces.
  4. Provision AWS, Azure, or both private data backends.
  5. Add provider connection manifests to the coordinating node.
  6. Sync local project data to the connected backend surfaces.
  7. Build and deploy the static frontend when users need browser access beyond the local workstation.

When both AWS and Azure are connected, the coordinating node syncs the provider-neutral project model across configured surfaces. The public static website bucket or container should not be treated as file or document-artifact storage.

Build

npm run build

Current Development Deployment

As of July 2, 2026, the development frontend has been published to both cloud static hosting targets:

Cloud Hosting resource Public URL CDN/DNS status
AWS S3 static website bucket workspace.categori.se in account 977060200223 http://workspace.categori.se.s3-website-us-east-1.amazonaws.com CloudFront distribution E2N4VCNIOGFEIE serves https://workspace.categori.se.
Azure Storage static website account wme97ea6bcsite in resource group wm-site-rg https://wme97ea6bcsite.z13.web.core.windows.net/ No Azure Front Door/CDN or custom DNS is attached yet.

The production user-facing hostname is the AWS CloudFront URL. Signed-in workspace traffic calls the authenticated cloud API at https://ovetov6bl8.execute-api.us-east-1.amazonaws.com; that API currently persists the workspace envelope and sync bundle to AWS. Azure replication is available but disabled. The public static website bucket/container still serve only frontend files. Private file and document-artifact storage stays in authenticated backend object stores.

Independence Contract

AWS and Azure deploy independently. The AWS script reads only AWS_* settings and uploads to an S3 static website bucket. The Azure script reads only AZURE_* settings and uploads to the Azure Storage $web container. The only shared artifact is the local dist/ directory produced by npm run build.

Do not reuse one cloud's public hosting bucket/container, DNS name, or CDN distribution for the other cloud. Keep DNS/CDN front doors separate until a deliberate traffic-management layer is introduced.

AWS Frontend

AWS deployment uses a dedicated public S3 website bucket. AWS documents S3 static website hosting for static pages and client-side scripts, and the AWS CLI s3 sync command recursively copies changed files from dist/ to S3.

export AWS_REGION=us-east-1
export AWS_PROFILE=categorise
export AWS_SITE_BUCKET=workspace.categori.se
npm run deploy:aws

AWS_SITE_BUCKET defaults to workspace.categori.se when unset. Use AWS_PROFILE=categorise for account 977060200223. Set AWS_SITE_DELETE=true only when you want the script to remove remote files that are no longer in dist/.

The script also uploads extensionless HTML aliases, for example docs/deployment.html is also published as docs/deployment, so Observable Framework's clean routes work on plain S3 website hosting. When AWS_SITE_DOMAIN or AWS_SITE_DISTRIBUTION_ID is set, deploy:aws creates a CloudFront invalidation after upload unless AWS_SITE_INVALIDATE=false.

AWS Auth Gateway

The workspace frontend uses the same application-side gateway pattern as the other categori.se apps. Unauthenticated users see a centered sign-in panel with Sign in, Register, Demo, and Recover account actions. Sign-in, registration, password recovery, and sign-out use Cognito Hosted UI with PKCE.

The shared gateway UI and actions live in src/components/auth-gateway.js. The mounted workspace routes use it from src/components/workspace-management-app.js, and the site-wide Observable head script in src/workspace-chrome.js uses the same component to guard static pages such as / and /docs/*. ?demo=1 or ?preview=1 opens the public demo route without a Cognito session.

Authenticated Cloud API

The deployed workspace API is an AWS HTTP API Gateway endpoint backed by Lambda:

Setting Value
API endpoint https://ovetov6bl8.execute-api.us-east-1.amazonaws.com
Lambda function workspace-management-api
API Gateway authorizer Cognito JWT authorizer for us-east-1_KXUwaZnuQ and client 56jt8rqj4r0i2hafjper8ljsmb
Frontend runtime config observablehq.config.js sets WORKSPACE_MANAGEMENT_CONFIG.apiBase for workspace.categori.se and the CloudFront distribution hostname.

The API exposes the same browser contract as local development:

The deployed Lambda writes workspace envelopes and sync bundles to AWS by default:

Provider Object store Metadata/index store
AWS S3 bucket workspace-management-dev-documents DynamoDB table workspace-management-dev
Azure (optional; currently disabled) Configured Blob Storage container Configured Cosmos DB database and container

The session banner uses the production runtime config label AWS with detail AWS S3 + DynamoDB after the authenticated API successfully hydrates workspace state. If that API is configured but unavailable, the UI reports Cloud unavailable instead of Local fallback.

Azure replication switch

Azure reads, writes, object checks, and read repair are gated by WORKSPACE_AZURE_SYNC_ENABLED. The default is false; in that mode, the deployment does not call the Azure CLI, the Lambda does not contact Azure, and Azure credentials are removed from the Lambda environment.

To restore Azure replication later, authenticate to the desired Azure account, set the new resource names, and redeploy both API and frontend with the switch enabled:

export WORKSPACE_AZURE_SYNC_ENABLED=true
export AZURE_RESOURCE_GROUP=your-resource-group
export AZURE_STORAGE_ACCOUNT=yourstorageaccount
export AZURE_COSMOS_ACCOUNT=yourcosmosaccount
npm run deploy:cloud-api
npm run deploy:aws

Set WORKSPACE_AZURE_SYNC_ENABLED=false (or leave it unset) and rerun npm run deploy:cloud-api to return to AWS-only mode.

Public Demo Account

The Demo action opens a dedicated browser-local Workspace Demo account instead of bypassing auth with the production seed. The seed lives in cloneDemoWorkspaceSeed(), and the daily browser store and quotas live in src/lib/demo-workspace.js.

Demo behavior:

Current auth resources:

Setting Value
AWS account 977060200223
User pool us-east-1_KXUwaZnuQ (auth)
Hosted UI domain auth.categori.se
App client 56jt8rqj4r0i2hafjper8ljsmb (workspace-management-spa)
Callback/logout URLs https://workspace.categori.se/, https://d28y8aivxdwrn7.cloudfront.net/, local ports 3000-3002

The gateway protects application routes and static documentation entry points from casual unauthenticated browsing, and keeps private workspace flows behind a Cognito session before workspace data hydration. API Gateway enforces the Cognito bearer token for cloud persistence. The static S3 website bucket remains a public origin because this deployment uses S3 website hosting; private workspace data is accessed through the authenticated API.

AWS HTTPS Custom Domain

Use setup:aws:site-domain after the S3 website bucket has been created by deploy:aws. The script creates or reports one CloudFront distribution for the requested alias, uses the S3 website endpoint as the origin, redirects HTTP viewers to HTTPS, and maps 403/404 responses back to index.html for clean Observable routes.

For the current workspace.categori.se deployment:

export AWS_REGION=us-east-1
export AWS_PROFILE=categorise
export AWS_SITE_BUCKET=workspace.categori.se
export AWS_SITE_DOMAIN=workspace.categori.se
export AWS_SITE_CERTIFICATE_ARN=arn:aws:acm:us-east-1:977060200223:certificate/79ce96a2-6b18-48ac-b334-abdc0a5dc25d
npm run deploy:aws
npm run setup:aws:site-domain

The active target-account CloudFront distribution is:

Setting Value
Account 977060200223
Distribution id E2N4VCNIOGFEIE
Distribution domain d28y8aivxdwrn7.cloudfront.net
Alias workspace.categori.se
Origin workspace.categori.se.s3-website-us-east-1.amazonaws.com
Certificate arn:aws:acm:us-east-1:977060200223:certificate/79ce96a2-6b18-48ac-b334-abdc0a5dc25d

Because categori.se is not hosted in Route53 in this AWS account, manage DNS in DNSMadeEasy or the active DNS provider. The production DNS record should point the subdomain to CloudFront:

workspace CNAME d28y8aivxdwrn7.cloudfront.net

Keep this ACM validation record in place so the certificate can renew automatically:

_bdede6a9b74260a9787d5e37958bf04e.workspace CNAME _9343714ced34b51e35ddc54df1b60b24.jkddzztszm.acm-validations.aws.

Current DNS and certificate findings:

If the intended hostname is exactly workspace.catgori.se, request and validate a separate us-east-1 ACM certificate and create DNS validation records in the authoritative DNS provider before running setup:aws:site-domain.

AWS Approval Notes

Ask IT to approve or provide:

The AWS frontend bucket is separate from the private S3 document bucket created by npm run setup:aws.

Azure Frontend

Azure deployment uses a dedicated StorageV2 account with static website hosting enabled and uploads dist/ into the $web container.

az login
az account set --subscription "<subscription-id-or-name>"

export AZURE_RESOURCE_GROUP=wm-site-rg
export AZURE_LOCATION=eastus
export AZURE_SITE_STORAGE_ACCOUNT=<globally-unique-lowercase-name>
npm run deploy:azure

The script defaults Blob uploads to AZURE_STORAGE_AUTH_MODE=key, which lets the deployment account use the storage account key immediately after creating the account. Set AZURE_STORAGE_AUTH_MODE=login only after assigning the deploying identity a Blob Data role on the storage account. Set AZURE_SITE_DELETE=true only when you want the script to clear $web before uploading.

The script also uploads extensionless HTML aliases, for example docs/deployment.html is also published as docs/deployment, so Observable Framework's clean routes work on Azure Storage static website hosting.

Azure Approval Notes

Ask IT to approve or provide:

The Azure frontend $web container is separate from the private Blob container created by npm run setup:azure. The first Azure deployment record is retained in Azure Backend Setup.

Deployment Approval Summary

Use this summary when requesting approval for either cloud.

Concern AWS frontend Azure frontend
Public hosting resource S3 static website bucket StorageV2 static website $web container
Deploy command npm run deploy:aws npm run deploy:azure
Cloud CLI aws az
Environment variables AWS_* AZURE_*
Least-privilege deploy identity IAM user or role scoped to the frontend bucket User, service principal, or managed identity with Blob data-plane write access
Public access model Public website endpoint unless fronted by an access layer Public anonymous $web endpoint unless fronted by an access layer
HTTPS/custom domain npm run setup:aws:site-domain creates CloudFront; DNS record is external when the zone is not in Route53 Azure Front Door, Azure CDN, or approved front door
Private file/artifact storage Separate private S3 bucket Separate private Blob container

Backend Baselines

Use these when you also want to create the private data resources:

npm run setup:aws
npm run setup:azure

The setup scripts create baseline private object storage and metadata databases. They do not deploy API compute, custom domains, HTTPS CDN fronts, identity callback apps, or email sender verification.

Internal Corporate Deployment

When the corporation already has cloud storage, ask IT to provide or approve a dedicated static-site storage target and a separate private file/artifact storage target.

For the frontend static site, request:

For private project data, request:

Do not use the public static website bucket/container for private files or document artifacts.

Command Separation Audit

The deployment scripts are intentionally independent.

Deployment Script Cloud CLI Public hosting resource Environment prefix
AWS frontend scripts/deploy-aws-site.sh aws S3 static website bucket AWS_
AWS custom domain scripts/setup-aws-site-domain.sh aws CloudFront distribution in front of S3 website bucket AWS_
Azure frontend scripts/deploy-azure-site.sh az Azure Storage static website $web AZURE_

The only shared step is the local static build:

npm run build

After that, AWS uploads to S3 and Azure uploads to the $web container. Neither deployment script calls the other cloud CLI, reads the other cloud's environment variables, or writes to the other cloud's public hosting resource.

The backend baseline scripts are also separate:

Backend Script Private object store Metadata database
AWS scripts/setup-aws-backend.sh S3 DynamoDB
Azure scripts/setup-azure-backend.sh Azure Blob Storage Cosmos DB

Production Notes

For AWS production hosting, prefer an approved HTTPS front door such as CloudFront or Amplify Hosting in front of S3 for CDN behavior and custom-domain TLS.

For Azure production hosting, use Azure Front Door or Azure CDN when you need custom-domain HTTPS, caching rules, and edge routing in front of the static website endpoint.

References