# Sites A repository can have a website. You deploy a directory of files, such as a build's output, and knotless serves it at `https://.knotless.page//`. The files are stored as blobs in your own account, in one record that each deploy replaces. [Pages](../design/pages.md) explains how sites are stored and served, and the [command reference](../reference/cli.md) lists every flag of `knotless repo site`. ## Deploy a site As the repository's owner, give `knotless repo site` the repository and the directory to deploy: ```sh knotless repo site knotless://alice.example.com/blog dist ``` It uploads only the files the live site does not hold already, then replaces the site's record. It prints how many it uploaded and the address: ``` uploaded 3 of 41 files https://alice-example-com.knotless.page/blog/ ``` The new site is live within seconds. A visitor sees the old site or the new one, never a mix of the two. The subdomain is your handle with each `-` doubled and each `.` turned into a `-`: `alice.example.com` is `alice-example-com`. It follows your handle, so it changes when your handle does. A site holds 1 to 2,000 files. ## Deploy a private site A private repository's site is deployed the same way. It is kept in the repository's space, and only its members can read it. It is served at a host of its own, named by the repository's key and your plc id: ``` uploaded 3 of 41 files https://3lbqz7ywmz22k-abcdefghijklmnopqrstuvwx.private.knotless.page/ ``` The address stays the same when the repository is renamed. The site is served at the host's root, so build it with `/` as its base path. A member who opens it is sent to knotless.dev to log in, if they are not already logged in, and comes back. Someone who is not a member gets a 404. A member you remove stops reading the site within a minute. ## Build for the path The site is served under `//`, not at the root, so build it with that as its base path. In Vite, that is `base`: ```js // vite.config.js export default { base: '/blog/' } ``` In SvelteKit it is `kit.paths.base`. A build with the wrong base loads its pages but not their scripts and styles. ## Serve a single-page app A single-page app routes in the browser, so every page has to load the app. Name the app's page as the fallback: ```sh knotless repo site knotless://alice.example.com/app dist --fallback index.html ``` A request for a page that no file matches then gets `index.html`, with status 200, and the app's router takes the path from there. A request counts as one for a page when it accepts `text/html`, or when the last part of its path has no extension. A file still comes first: `/app/assets/main.js` serves that file. A missing script or image gets a 404, not the app's HTML, so a broken build shows as one. ## Add a 404 page To answer a missing path with a page of your own, name it: ```sh knotless repo site knotless://alice.example.com/blog dist --not-found 404.html ``` A path that no file matches gets that page with status 404. With a fallback too, pages get the fallback, and other files get the 404 page. The fallback and the 404 page must each be one of the files you deploy. ## Deploy on every push The `site` shared step deploys from a workflow. It runs `knotless repo site` as the repository's owner, and installs knotless first if the image has none. The step logs in with an app password of the owner's account. Make one at your PDS (on Bluesky, under Settings, Privacy and security, App passwords). Then give it to the runners as a secret, limited to the branch you deploy from: ```sh printf %s "$APP_PASSWORD" | knotless secret knotless://alice.example.com/blog set SITE_PASSWORD --ref refs/heads/main ``` Then add a workflow that builds the site and deploys it, on a push to that branch only: ```yaml # .knotless/workflows/site.yml on: push: {refs: [refs/heads/main]} image: node:24 secrets: [SITE_PASSWORD] steps: - name: build run: npm ci && npm run build - uses: chadtmiller.com/steps/site@1 with: {dir: dist, fallback: index.html} secrets: [SITE_PASSWORD] ``` The step's inputs are `dir`, the directory to deploy, and optionally `fallback`, `not-found`, `repo` (this run's repository if not given) and `appview`. Pin it before you push, and commit the lock file: ```sh knotless uses update git add .knotless/uses.lock ``` Each deploy records the commit it was built from. [Shared steps](../reference/workflows.md#shared-steps) explains how `uses` works, and [Workflows](workflows.md) how to run a runner and give it secrets. ## See what is live With no directory, `knotless repo site` shows the live site: its address, how many files it holds, the commit it was built from, and when it was deployed: ```sh knotless repo site knotless://alice.example.com/blog ``` The repository's page shows the site's address too, under its description. You don't need to add the address to the repository's links. A private repository's site is shown only to its members. There is no history of deploys. To roll back, deploy an older commit's build again. ## Take a site down ```sh knotless repo site knotless://alice.example.com/blog --down ``` This deletes the site's record, and the site stops being served.