Appearance
Static Website Hosting
A static website hosting enables deployment of single page applications (SPAs) directly on Tailor Platform with optional IP-based access control and CDN caching.
Configuration
Define static websites in your workspace configuration. Without IP restrictions, websites are automatically cached via CDN for optimal performance.
typescript
import { defineStaticWebSite } from "@tailor-platform/sdk";
// Basic static website
export const mySpa = defineStaticWebSite("my-spa", {
description: "Single page application",
});
// With IP restrictions (disables caching)
export const internalApp = defineStaticWebSite("internal-app", {
description: "Internal application with restricted access",
allowedIpAddresses: ["198.51.100.0/24", "203.0.113.0/24"],
});Deployment
Deploy pre-built static files using TailorCLI. The service is designed for single page applications and will serve index.html for unmatched routes.
bash
# Deploy your build output directory
tailor staticwebsite deploy \
--name my-spa \
--dir ./dist \
--workspace-id <workspace-id>Caching Behavior
- Public websites (no IP restrictions): Automatically cached via CDN for optimal performance
- Restricted websites (with IP restrictions): Caching is disabled to ensure access control
Using Generated URLs
Each static website receives a unique URL that can be used in your application configuration. After deploying your static website, you can reference its URL in your application's CORS settings or auth redirect URLs:
typescript
import { defineConfig } from "@tailor-platform/sdk";
import { mySpa } from "./static-websites";
export default defineConfig({
name: "my-app",
// Add static website URL to CORS
cors: [mySpa.url],
staticWebsites: [mySpa],
});The static website URL format is: https://{static-website-name}-{WORKSPACE_HASH}.web.erp.dev
Custom Domains
You can use your own subdomain (e.g. app.example.com) instead of the auto-generated URL. Apex domains (e.g. example.com) are not yet supported. A domain can only belong to one static website. Adding a domain that is already in use fails, unless it has been offered to your workspace — accepting an offer is how moving a domain completes.
Each static website supports up to 10 custom domains. After adding a domain, you need to configure DNS CNAME records to complete the setup.
Adding Custom Domains
There are two ways to add custom domains to a static website.
SDK Configuration
Add the customDomains field to your defineStaticWebSite() definition. Domains are provisioned automatically when you run tailor deploy.
typescript
import { defineStaticWebSite } from "@tailor-platform/sdk";
export const mySpa = defineStaticWebSite("my-spa", {
description: "Single page application",
customDomains: ["app.example.com"],
});CLI
Use the API command to add a custom domain directly.
bash
tailor api AddCustomDomain --body '{
"workspace_id": "<workspace-id>",
"static_website_name": "my-spa",
"domain": "app.example.com"
}'You can list and inspect custom domains with dedicated subcommands:
bash
# List all custom domains for a static website
tailor staticwebsite domain list my-spa
# Get details of a specific custom domain
tailor staticwebsite domain get app.example.comDNS Configuration
After adding a custom domain, configure CNAME records with your DNS provider. The required targets are returned in the response and can also be retrieved with tailor staticwebsite domain get.
| Record Type | Host | Target | Purpose |
|---|---|---|---|
| CNAME | app.example.com | Value of trafficCnameTarget | Routes traffic to the CDN |
| CNAME | _acme-challenge.app.example.com | Value of certificateCnameTarget | TLS certificate issuance (DNS challenge) |
For new domains, setting the traffic CNAME is sufficient. The TLS certificate is issued automatically via HTTP challenge. If you are migrating from another service and cannot switch traffic yet, set the _acme-challenge record first to issue the certificate via DNS challenge, then update the traffic CNAME when ready.
Moving a Domain
A domain already serving traffic can be moved to another static website, in the same workspace or a different one, without taking it offline. The TLS certificate, the CDN registration and your DNS records all stay as they are, so the domain keeps serving throughout and no re-issuance or DNS change is needed.
A move is a handover in two steps: the website that currently holds the domain offers it to a workspace, and the receiving website accepts by taking the domain on. Each side acts in its own workspace, so nobody needs permission on both.
Offering the Domain
In the configuration of the website giving the domain up, replace the plain entry with the destination workspace:
typescript
import { defineStaticWebSite } from "@tailor-platform/sdk";
export const mySpa = defineStaticWebSite("my-spa", {
customDomains: [
{ domain: "app.example.com", transferTo: { workspaceId: "<target-workspace-id>" } },
],
});tailor deploy opens the offer and reports it on every later deploy until it is taken up. The domain goes on serving the current website in the meantime.
A domain that is not managed through customDomains can be offered through the API instead — see Moving a Domain Through the API.
Accepting the Domain
On the receiving side, list the domain on the website that should take it over and deploy:
typescript
export const newSpa = defineStaticWebSite("new-spa", {
customDomains: ["app.example.com"],
});That deploy completes the move. Accepting is an ordinary custom domain entry, so a receiving workspace needs no special configuration, and the two deploys must be separate runs even when both websites live in the same workspace.
After the Move
The giving side's next plan reports that the domain has moved and the entry can be deleted — it now describes a move that already happened rather than anything to maintain. Remove it and deploy once more to finish.
While an offer is open:
- It expires after 7 days. Deploying the entry again after that opens a fresh offer.
- Neither the domain nor the website holding it can be removed. Withdraw the offer first: revert the entry to a plain string and deploy, or call
CancelCustomDomainTransfer. - An offer takes a moment to become ready. Until it is, the receiving side cannot accept it and its deploy is refused.
Moving a Domain Through the API
A domain that is not listed in a customDomains entry — one added with tailor api AddCustomDomain, or managed outside the SDK — is moved through the API instead. The receiving side still accepts the same way, by listing the domain and deploying, or with tailor api AddCustomDomain.
bash
tailor api RequestCustomDomainTransfer --body '{
"workspace_id": "<source-workspace-id>",
"domain": "app.example.com",
"target_workspace_id": "<target-workspace-id>"
}'Withdraw an offer, and inspect the ones you have open:
bash
# Withdraw an offer that has not been accepted yet
tailor api CancelCustomDomainTransfer --body '{
"workspace_id": "<source-workspace-id>",
"domain": "app.example.com"
}'
# Check a single domain's transfer
tailor api GetCustomDomainTransfer --body '{
"workspace_id": "<workspace-id>",
"domain": "app.example.com"
}'
# List transfers, optionally filtered by status
tailor api ListCustomDomainTransfers --body '{
"workspace_id": "<workspace-id>",
"status": "CUSTOM_DOMAIN_TRANSFER_STATUS_PENDING"
}'Both sides can read a transfer; only the workspace giving the domain up can cancel it.
Do not mix the API offer with a customDomains entry for the same domain
If the domain is still listed as a plain string in the giving side's customDomains, that entry and an API-opened offer work against each other:
- While the offer is open, the next
tailor deployon the giving side withdraws it. The planner reads a plain-string entry with an open offer as an offer you reverted, and cancels it. It appears in the plan as aCustomDomainTransfersline, which is easy to miss in a pipeline runningtailor deploy -y. - After the move completes, the entry names a domain the website no longer holds, so the next deploy tries to add it back and fails with
AlreadyExists: same domain already exists— the domain now belongs to the other workspace. - Removing the entry while the offer is open is refused with
CUSTOM_DOMAIN_TRANSFER_IN_FLIGHT.
So either use the transferTo entry, or make sure the giving side does not deploy between the API call and the receiving side's acceptance, then delete the entry once the move is done.
The customDomains field is documented in full on the Static Website SDK reference.
Domain Status
Custom domains progress through the following statuses:
| Status | Description |
|---|---|
pending | Domain registered, challenge not yet started |
verifying | Challenge in progress, verifying certificate |
cert_issued | TLS certificate issued, waiting for traffic CNAME |
active | TLS certificate active and domain is serving traffic |
failed | Provisioning failed (see errorMessage for details) |
removing | Domain is being removed and cannot be re-added yet |
If the challenge does not complete within 7 days, the domain is automatically removed. To retry, add the domain again.