Deployment
Overview
Deploying a Bascik site is fast and flexible. Because Bascik compiles your components, styles, and scripts into standard vanilla HTML, CSS, and client JavaScript in dist/, you can host your site anywhere: from zero-configuration static hosts and global CDNs to edge serverless platforms and dedicated Node servers.
Choosing Where to Deploy
Most websites built with Bascik fall into one of three deployment models:
1. Static Hosting (Most Common)
If your site is a marketing page, blog, documentation site, portfolio, or uses build-time scripts (data-bascik-build) to fetch data, static hosting is all you need.
- How it works: Run
bascik --buildto producedist/and upload it to any host or CDN. - Popular hosts: Cloudflare Pages, GitHub Pages, Netlify, Vercel, AWS S3 / CloudFront, and traditional web servers (NGINX, Caddy, Apache).
- Zero runtime maintenance: No Node.js process to keep alive in production, zero compute charges, and instant global caching.
Get Started: Follow our step-by-step, hand-holding Static Hosting Guide to deploy your site to Cloudflare Pages, GitHub Pages, Netlify, and more in minutes.
2. Serverless & Edge Adapters (Cloudflare Workers & Pages)
If your site uses request-time server scripts (data-bascik-server), progressive HTML streams (data-bascik-stream), or edge API routes (src/api/), you can deploy to edge platforms without managing a dedicated server.
- How it works: Running
bascik --build --target cloudflarecompiles static assets for the CDN while packaging your server scripts, stream scripts, and API routes into an edge Worker automatically. - Benefits: Global edge execution, zero origin servers to maintain, and automatic streaming.
Read the Guide: Check out the Cloudflare Adapter guide for build commands, Wrangler configuration, and edge features, or learn about Custom Adapters.
3. Node.js Production Server (bascik --server)
If you prefer self-hosting on a VPS, Docker container, or cloud virtual machine, Bascik includes a built-in production HTTP/1.1 and HTTP/2 server.
- How it works: Run
bascik --buildfollowed bybascik --server. - Capabilities: High-throughput HTTP/2, automatic Brotli and Gzip compression, health-check probes, and zero-downtime draining.
Read the Guide: See Production Server and Server Scripts for configuration details.
Static hosting
For most Bascik sites, dist/ is the deployable artifact. You only need a static host when nothing on the site runs at request time: no data-bascik-server scripts, no data-bascik-stream scripts, and no API route files in src/api/. Each of those needs something to execute code per request, either the built-in Node server or a serverless target.
Every major platform follows the same pattern:
- Run
bascik --buildto producedist/ - Configure the host to deploy from the
dist/folder - Point the publish directory at
dist/
For step-by-step walkthroughs across popular platforms, see the dedicated Static Hosting guide.
Serverless hosting
Serverless here means you do not operate Bascik's Node server: a CDN serves the static files and a managed function runs your server scripts, stream scripts, and API routes per request. Bascik builds this as an explicit, opt-in target so the default dist/ stays a plain static tree.
Hosting adapters are installable packages that implement the @bascik/bascik/adapter contract. Official targets include cloudflare-pages and cloudflare-workers via @bascik/adapter-cloudflare. Third parties can publish custom adapters using @bascik/bascik/adapter and runtime helpers from @bascik/bascik/runtime. See Cloudflare Adapter for the tested recipe, support matrix, and provider limits, or read Custom Adapters to learn how to author custom deployment adapters.
Using the production server
If your site uses data-bascik-server scripts for per-request dynamic content, you need infrastructure that can execute Node.js alongside the built files. The built-in production server handles this without any additional framework.
bascik --build # compile to dist/
bascik --server # start the HTTP server; runs server scripts per request See Production Server for full documentation on server configuration and Server Scripts for the request context API.
Per-environment values: the site URL
The site URL is a per-deployment value, so it is not a config-file key. Set BASCIK_SITE_URL in each environment's configuration (CI variables, container env, a .env file on the target) and the same checked-in source builds for staging and production without mutating anything:
BASCIK_SITE_URL=https://staging.example.com bascik --build # staging
BASCIK_SITE_URL=https://example.com bascik --build # production A --site-url flag and an automatic ./.env file are also available; see Configuration for the precedence chain. Builds that generate a sitemap or robots.txt fail when no source provides the URL, so a misconfigured environment surfaces immediately instead of shipping a broken sitemap.
What's in dist/
Running bascik --build produces:
- HTML: compiled pages with component tags resolved, scoped class names applied, build-script output inlined, and dynamic route templates expanded into concrete static HTML files
- CSS and JS: page-adjacent files from
src/pages/, processed by configured minifiers - Static assets: eligible images, fonts, downloads, and other files from
src/pages/, preserving their relative paths
The output uses root-relative paths (e.g. /css/styles.css). Files must be served from an HTTP server; opening them directly with file:// will break asset loading.
Builds are reproducible and deterministic: identical source inputs always produce byte-identical output across repeated runs and machines. This makes it straightforward to diff dist/ between builds or verify deployed artifacts against the exact commit that produced them.
Every full dev or build run cleans directory.out before pre-phase lifecycle scripts run. The output therefore reflects the current source tree, without pages or assets left behind by earlier runs. Pre-phase scripts can still generate files in the output directory because cleaning finishes before those scripts start. bascik --server only reads an existing build and never cleans it. Targeted builds (bascik --build --only <glob>) also skip cleaning so existing pages survive when rebuilding a small subset.
Consuming the build manifest
When generate.manifest: true is configured, Bascik outputs dist/.bascik/manifest.json. Deployment workflows and CDN synchronization scripts can consume this manifest to upload only modified files or verify build outputs:
// Example deploy-layer script reading dist/.bascik/manifest.json
import { readFileSync } from 'node:fs';
const manifest = JSON.parse(readFileSync('dist/.bascik/manifest.json', 'utf8'));
for (const [relPath, info] of Object.entries(manifest.files)) {
console.log(`Deploying ${relPath} (${info.size} bytes, SHA-256: ${info.hash})`);
} Generating strict Content Security Policy headers
Bascik inlines component <style> blocks and wraps component <script> blocks in isolated IIFEs. To support strict CSP configurations without using 'unsafe-inline', enable generate.cspHashes: true in bascik.config.ts. Bascik emits dist/.bascik/csp-hashes.json mapping each page to its exact post-minification inline script and style SHA-256 hashes (sha256-<base64>).
Bascik emits hashes rather than injecting a CSP header because CSP headers belong to your hosting provider or CDN edge. Setting a generic CSP with 'unsafe-inline' inside the framework would provide false assurance.
Cross-Origin Isolation Headers
Bascik sets safe default cross-origin headers:
Cross-Origin-Opener-Policy: same-origin-allow-popupsCross-Origin-Resource-Policy: cross-origin
These defaults allow cross-origin images, fonts, and authentication popups to function without unexpected breaks. If full cross-origin isolation (e.g. SharedArrayBuffer) is required, configure Cross-Origin-Embedder-Policy: require-corp at your hosting layer.
// scripts/generate-csp-headers.ts
import { readFileSync, writeFileSync } from 'node:fs';
const hashes = JSON.parse(readFileSync('dist/.bascik/csp-hashes.json', 'utf8'));
let headers = '';
for (const [path, pageHashes] of Object.entries(hashes)) {
const scriptSrc = pageHashes.scripts.map((h) => `'${h}'`).join(' ');
const styleSrc = pageHashes.styles.map((h) => `'${h}'`).join(' ');
headers += `${path}\n Content-Security-Policy: script-src 'self' ${scriptSrc}; style-src 'self' ${styleSrc}\n\n`;
}
writeFileSync('dist/_headers', headers); Excluded source files
To keep deployment artifacts clean, the following files are excluded from static asset copying and are never copied to dist/:
- Component source files: all files in
src/components/are source templates, resolved at build time, and never copied todist/ - Page templates:
.htmlfiles insrc/pages/are transpiled into compiled pages - TypeScript files:
.tssource files used by build scripts or helper modules - Other source files:
.mjs,.cjs,.mts, and.ctsmodules, source maps (.map), and Markdown (.md) - API route handlers: files in
src/api/(directory.api) are runtime handlers executed in server mode and are never copied to staticdist/. Because handler source code is strictly protected and never published, accessing private environment secrets viaprocess.envin handlers remains secure. - Test files: any test file matching
*.test.*or*.spec.*(e.g.styles.test.ts) - Inlined stylesheets: global CSS files configured in
assets.inlineStyles(injected directly into<head>) - Hidden paths: every dotfile and every file below a dot-directory
- Dependencies: every file below a
node_modulesdirectory
Treat directory.pages as the publish tree. Colocate assets with a page or organize shared files under folders such as src/pages/assets/, src/pages/images/, and src/pages/fonts/. Keep tests and source-only helpers outside that tree. Use assets.exclude for project-specific exclusions; its globs match relative to directory.pages, and the built-in exclusions always apply.
If a project needs to copy files from a separate source tree, use a pipeline.exec script that selects those files and writes them to directory.out. This keeps external copying explicit instead of creating a second built-in asset root.
Previewing static builds locally
To preview your built site locally before deploying, run Bascik's built-in production server:
bascik --server Or preview with any third-party static HTTP server:
npx http-server dist Then open http://localhost:8080 in your browser to inspect your production site.
Subdirectory deploys
Set base when the site is published at a path such as https://example.com/docs/ instead of the domain root. GitHub Pages project sites are a common example: a repository named my-site is normally published at https://account.github.io/my-site/.
// bascik.config.ts
import { defineConfig } from '@bascik/bascik/config';
export default defineConfig({
base: '/my-site/',
}); Bascik normalizes the leading and trailing slash, rewrites root-relative HTML, CSS, and web app manifest URLs during the build, and serves pages and static assets below the same prefix in development and with bascik --server. Generated sitemap, robots, and canonical URLs compose the site URL, base, and page path in that order.
Requests outside the configured prefix return 404 Not Found. With base: '/my-site/', request /my-site/about, not /about. This strict behavior matches a static host and catches incorrect links during local preview. Live reload also connects through the prefix automatically.
A custom domain mapped to the project site usually serves it from /, so leave the default base: '/' in that deployment shape.
Reverse proxy and CDN deployments (trustProxy)
When deploying bascik --server behind a CDN, load balancer, or reverse proxy (such as Cloudflare, AWS CloudFront/ALB, or NGINX), set http.trustProxy: true in bascik.config.ts (or under export const server):
export const server = defineConfig({
http: {
trustProxy: true,
},
}); When trustProxy: true is enabled:
- Rate limiting derives client IP from the rightmost (immediate proxy) entry of
X-Forwarded-For, preventing a single active visitor from exhausting the rate-limit budget for all visitors behind the proxy. - HSTS security headers recognize
X-Forwarded-Proto: httpsforwarded by the proxy.
When trustProxy: false (the default), X-Forwarded-For and X-Forwarded-Proto headers are strictly ignored to prevent client spoofing. Do not enable trustProxy if the server is directly exposed to the public Internet without a trusted reverse proxy.
Health checks and zero-downtime deployments
bascik --server provides built-in endpoints for load balancer and orchestrator health checks:
- Liveness probe:
GET /_health/livereturns200 OKas long as the process is alive. - Readiness probe:
GET /_health(orGET /_health/ready) returns200 OKwhen the server is ready to accept traffic, and503 Service Unavailableduring boot and during the shutdown drain window.
Configure your container orchestrator (e.g. Kubernetes, AWS ECS) or load balancer with:
- Health check path:
/_health - Shutdown signal:
SIGTERM - Deregistration delay: Match or exceed
http.timeouts.drain(default5000ms) so the load balancer stops routing new traffic before the process exits.