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:
- Run locally with
npm run dev:local. - Confirm the local API writes metadata to
.wm-data/workspace.sqliteand file bytes to.wm-data/objects/. - Create application accounts for clients, businesses, departments, firms, or personal workspaces.
- Provision AWS, Azure, or both private data backends.
- Add provider connection manifests to the coordinating node.
- Sync local project data to the connected backend surfaces.
- 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 development 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. |
Both targets are development static frontends. They do not store private project files. Private file and document-artifact storage should remain in the selected backend object store and should be accessed only through authenticated API flows.
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.
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:
- The demo user has full UI authority inside the sandbox so visitors can inspect project, file, team, account, sharing, settings, and metadata workflows.
- Three example collaborators appear in account and team membership: Priya Shah, Omar Chen, and Sofia Rivera.
- Demo changes are saved in local browser storage for the current local date, then reset back to the seed on the next local date.
- Visitors can create at most 10 new projects per local day.
- Visitors can stage or upload at most 300 MiB of files per local day, enough for roughly a dozen large photos or similar media files.
- These limits are client-side demo guardrails, not a replacement for backend authorization or storage quotas on production APIs.
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. The static S3 website bucket remains a public origin because this deployment uses S3 website hosting; backend APIs and private file/object access still need server-side authorization checks.
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:
categori.seis delegated to DNSMadeEasy name servers.- Account
977060200223hosts theworkspace.categori.seS3 website bucket. - Account
977060200223has an issued us-east-1 ACM certificate forworkspace.categori.se. - No Route53 hosted zone for
categori.seis visible in this AWS account. workspace.categori.seshould have an explicit DNSMadeEasy CNAME tod28y8aivxdwrn7.cloudfront.netso it does not fall through to an unrelated wildcard route.catgori.sedid not return delegated name servers during the July 2, 2026 deployment check, and no matching ACM certificate was found.
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:
- A dedicated public S3 static website bucket for the target environment.
- A named deployment identity, such as a user, CI role, or assumed role, with write access only to that frontend bucket.
- Permission for the deployment script to configure website hosting, bucket policy, and public-read behavior for generated static files.
- Confirmation that no private project files, document artifacts, credentials,
.wm-datafiles, or backend secrets are published to the frontend bucket. - A DNS and HTTPS decision. Plain S3 website endpoints do not provide custom-domain HTTPS by themselves; this repo uses CloudFront for the scripted AWS custom-domain path.
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:
- Registration of the
Microsoft.Storageresource provider on the subscription, if it is not already registered. - A dedicated resource group and StorageV2 account for the public static frontend.
- Static website hosting enabled with
index.htmlas both the index and error document. - A named deployment identity with
Storage Blob Data Contributorscoped to the storage account or$webcontainer when usingAZURE_STORAGE_AUTH_MODE=login. - A key policy decision. If shared keys are disallowed, use
AZURE_STORAGE_AUTH_MODE=loginafter RBAC propagates. - Confirmation that Azure Storage static website content is anonymous read-only from
$web; it is not private Entra-authenticated hosting by itself. - A DNS and HTTPS decision. Custom-domain HTTPS should usually be reviewed through Azure Front Door or Azure CDN.
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:
- A dedicated AWS S3 website bucket or Azure Storage static website account for the development environment.
- A named deployment identity, such as a user, service principal, or CI role, with write access only to that static website bucket/container.
- Permission to publish generated
dist/files and extensionless HTML route aliases. - Confirmation that static website content is public unless placed behind CloudFront, Azure Front Door, a reverse proxy, VPN, or another corporate access-control layer.
- DNS and HTTPS ownership decision: S3 website endpoints need CloudFront for HTTPS/custom domains; Azure Storage static websites normally need Azure Front Door or Azure CDN for custom-domain HTTPS.
For private project data, request:
- A separate private object/blob container for raw files and derived artifacts.
- Data-plane write/read permissions only for the backend API or worker identity, not for anonymous static website users.
- Encryption, lifecycle retention, logging, and backup settings that match corporate data classification.
- A metadata store, such as DynamoDB or Cosmos DB, with access restricted to the backend API identity.
- A secrets location, such as AWS Secrets Manager or Azure Key Vault, for API credentials and integration secrets.
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
- AWS: Hosting a static website using Amazon S3
- AWS CLI:
aws s3 sync - Azure: Static website hosting in Azure Storage
- Azure CLI:
az storage blob upload-batch