| layout | title | parent | nav_order |
|---|---|---|---|
default |
Chapter 8: Deployment and Sharing |
Dyad Tutorial |
8 |
Welcome to Chapter 8: Deployment and Sharing. In this part of Dyad Tutorial: Local-First AI App Building, you will build an intuitive mental model first, then move into concrete implementation details and practical production tradeoffs.
You have built, styled, and tested your Dyad application -- now it is time to get it in front of users. Deployment transforms your local development project into a live, accessible application. In this final chapter, we will cover the full deployment lifecycle: building optimized production bundles, choosing the right hosting platform, configuring CI/CD pipelines, setting up custom domains, and sharing your projects with collaborators and the world.
A well-structured deployment pipeline takes your code from a local repository to a production environment with confidence. Dyad applications follow standard modern web deployment patterns.
flowchart TD
A[Local Development] --> B[Git Push]
B --> C[CI Pipeline Triggered]
C --> D[Install Dependencies]
D --> E[Lint & Type Check]
E --> F[Run Tests]
F --> G[Build Production Bundle]
G --> H{Tests Passed?}
H -->|Yes| I[Deploy to Staging]
H -->|No| J[Notify Developer]
I --> K[Smoke Tests]
K --> L{Staging OK?}
L -->|Yes| M[Deploy to Production]
L -->|No| J
M --> N[Health Check]
N --> O[Live Application]
classDef local fill:#e1f5fe,stroke:#01579b
classDef ci fill:#fff3e0,stroke:#ef6c00
classDef check fill:#f3e5f5,stroke:#4a148c
classDef deploy fill:#e8f5e8,stroke:#1b5e20
classDef fail fill:#ffebee,stroke:#b71c1c
class A,B local
class C,D,E,F,G ci
class H,K,L check
class I,M,N,O deploy
class J fail
Before deploying, you need an optimized production build. Dyad applications use Vite, which produces highly optimized bundles with tree shaking, code splitting, and asset optimization.
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import path from "path";
export default defineConfig({
plugins: [react()],
build: {
outDir: "dist",
sourcemap: true,
minify: "terser",
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true,
},
},
rollupOptions: {
output: {
manualChunks: {
vendor: ["react", "react-dom"],
router: ["react-router-dom"],
ui: ["@headlessui/react"],
},
},
},
chunkSizeWarningLimit: 500,
},
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
});# Create production build
npm run build
# Preview the production build locally
npm run preview
# Analyze bundle size
npx vite-bundle-visualizer| Optimization | Technique | Impact |
|---|---|---|
| Tree Shaking | Remove unused exports | 20-40% size reduction |
| Code Splitting | Lazy load routes and heavy components | Faster initial load |
| Minification | Terser compression | 60-70% size reduction |
| Asset Hashing | Content-based filenames | Optimal cache invalidation |
| Gzip / Brotli | Server-level compression | 70-80% transfer reduction |
| Image Optimization | WebP conversion, lazy loading | Significant bandwidth savings |
Vercel offers the smoothest deployment experience for React applications with automatic CI/CD, edge functions, and a global CDN.
flowchart LR
A[Git Push] --> B[Vercel Detects Change]
B --> C[Build Triggered]
C --> D[Deploy to Preview]
D --> E{PR Merged?}
E -->|Yes| F[Deploy to Production]
E -->|No| G[Preview URL Available]
F --> H[Global CDN]
H --> I[Edge Network]
I --> J[Users Worldwide]
classDef trigger fill:#e1f5fe,stroke:#01579b
classDef build fill:#fff3e0,stroke:#ef6c00
classDef deploy fill:#e8f5e8,stroke:#1b5e20
classDef edge fill:#f3e5f5,stroke:#4a148c
class A trigger
class B,C build
class D,E,F,G deploy
class H,I,J edge
{
"buildCommand": "npm run build",
"outputDirectory": "dist",
"installCommand": "npm install",
"framework": "vite",
"rewrites": [
{ "source": "/(.*)", "destination": "/index.html" }
],
"headers": [
{
"source": "/assets/(.*)",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=31536000, immutable"
}
]
},
{
"source": "/(.*)",
"headers": [
{
"key": "X-Content-Type-Options",
"value": "nosniff"
},
{
"key": "X-Frame-Options",
"value": "DENY"
},
{
"key": "X-XSS-Protection",
"value": "1; mode=block"
}
]
}
]
}# Install Vercel CLI
npm install -g vercel
# Login to Vercel
vercel login
# Deploy to preview
vercel
# Deploy to production
vercel --prod
# Set environment variables
vercel env add VITE_API_BASE_URL production
vercel env add VITE_GA_MEASUREMENT_ID productionNetlify provides powerful deployment with built-in form handling, serverless functions, and split testing.
# netlify.toml
[build]
command = "npm run build"
publish = "dist"
[build.environment]
NODE_VERSION = "20"
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
[[headers]]
for = "/assets/*"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"
[[headers]]
for = "/*"
[headers.values]
X-Content-Type-Options = "nosniff"
X-Frame-Options = "DENY"
Referrer-Policy = "strict-origin-when-cross-origin"
Content-Security-Policy = "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https:;"# Install Netlify CLI
npm install -g netlify-cli
# Login to Netlify
netlify login
# Initialize project
netlify init
# Deploy to preview (draft)
netlify deploy
# Deploy to production
netlify deploy --prod
# Set environment variables
netlify env:set VITE_API_BASE_URL https://api.example.comFor full control over your deployment environment, Docker containers offer portability and consistency.
# Build stage
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Production stage
FROM nginx:alpine AS production
# Copy custom nginx config
COPY nginx.conf /etc/nginx/conf.d/default.conf
# Copy built assets from build stage
COPY --from=build /app/dist /usr/share/nginx/html
# Add health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget -qO- http://localhost:80/health || exit 1
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]# nginx.conf
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
# Health check endpoint
location /health {
access_log off;
return 200 "OK";
add_header Content-Type text/plain;
}
# Serve static assets with long cache
location /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Enable gzip compression
gzip on;
gzip_types text/plain text/css application/json application/javascript
text/xml application/xml application/xml+rss text/javascript;
gzip_min_length 256;
# SPA fallback - serve index.html for all routes
location / {
try_files $uri $uri/ /index.html;
}
# Security headers
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
}# docker-compose.yml
version: "3.9"
services:
app:
build:
context: .
dockerfile: Dockerfile
ports:
- "3000:80"
environment:
- NODE_ENV=production
depends_on:
- api
restart: unless-stopped
api:
build:
context: ./api
dockerfile: Dockerfile
ports:
- "4000:4000"
environment:
- DATABASE_URL=postgresql://user:password@db:5432/dyad_app
- API_SECRET_KEY=${API_SECRET_KEY}
depends_on:
- db
restart: unless-stopped
db:
image: postgres:16-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
- POSTGRES_DB=dyad_app
restart: unless-stopped
volumes:
postgres_data:# Build and start all services
docker compose up -d --build
# View logs
docker compose logs -f app
# Stop all services
docker compose down| Feature | Vercel | Netlify | Docker / Self-Host | GitHub Pages |
|---|---|---|---|---|
| Free Tier | Yes | Yes | No (server cost) | Yes |
| Custom Domains | Yes | Yes | Yes | Yes |
| SSL Certificates | Automatic | Automatic | Manual / Let's Encrypt | Automatic |
| Serverless Functions | Yes | Yes | Manual setup | No |
| Global CDN | Yes | Yes | Optional (Cloudflare) | Yes |
| Preview Deployments | Automatic | Automatic | Manual | No |
| Build Minutes / Month | 6000 | 300 | Unlimited | 2000 |
| Backend Support | Edge Functions | Functions | Full control | Static only |
| Best For | React/Next.js SPAs | JAMstack sites | Complex architectures | Simple static sites |
Automate your deployment pipeline with GitHub Actions to ensure every change goes through testing before reaching production.
# .github/workflows/deploy.yml
name: Build, Test, and Deploy
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
NODE_VERSION: "20"
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Lint
run: npm run lint
- name: Type check
run: npx tsc --noEmit
- name: Run unit and integration tests
run: npm run test:coverage
- name: Upload coverage
uses: codecov/codecov-action@v4
with:
files: ./coverage/coverage-final.json
build:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
env:
VITE_API_BASE_URL: ${{ secrets.VITE_API_BASE_URL }}
VITE_GA_MEASUREMENT_ID: ${{ secrets.VITE_GA_MEASUREMENT_ID }}
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
e2e:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- name: Run E2E tests
run: npx playwright test
- name: Upload test report
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: playwright-report/
deploy:
needs: [test, build, e2e]
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- name: Deploy to Vercel
uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
vercel-args: "--prod"Setting up a custom domain gives your application a professional identity.
| Record Type | Name | Value | TTL |
|---|---|---|---|
| A | @ |
Platform IP (e.g., 76.76.21.21) |
3600 |
| CNAME | www |
your-app.vercel.app |
3600 |
| TXT | @ |
Domain verification string | 3600 |
flowchart LR
A[Configure DNS] --> B[Platform Detects Domain]
B --> C[Request SSL Certificate]
C --> D[Domain Validation]
D --> E[Certificate Issued]
E --> F[HTTPS Enabled]
F --> G[Auto-Renewal]
classDef config fill:#e1f5fe,stroke:#01579b
classDef process fill:#fff3e0,stroke:#ef6c00
classDef done fill:#e8f5e8,stroke:#1b5e20
class A config
class B,C,D process
class E,F,G done
Dyad makes it easy to share projects with others through multiple channels.
# Export as a zip archive
zip -r my-dyad-app.zip . -x "node_modules/*" ".git/*" "dist/*"
# Or use git archive
git archive --format=zip --output=my-dyad-app.zip HEADCreate a comprehensive setup script that new collaborators can run:
// scripts/setup.ts
import { execSync } from "child_process";
import { existsSync, copyFileSync } from "fs";
function run(cmd: string): void {
console.log(`Running: ${cmd}`);
execSync(cmd, { stdio: "inherit" });
}
function setup(): void {
console.log("Setting up Dyad application...\n");
// Check Node.js version
const nodeVersion = process.version;
const major = parseInt(nodeVersion.slice(1).split(".")[0]);
if (major < 18) {
console.error("Node.js 18 or later is required");
process.exit(1);
}
console.log(`Node.js ${nodeVersion} detected\n`);
// Install dependencies
run("npm install");
// Set up environment file
if (!existsSync(".env.local")) {
if (existsSync(".env.example")) {
copyFileSync(".env.example", ".env.local");
console.log("\nCreated .env.local from .env.example");
console.log("Please update .env.local with your actual values\n");
}
}
// Run database migrations if applicable
if (existsSync("prisma/schema.prisma")) {
run("npx prisma generate");
run("npx prisma db push");
console.log("\nDatabase schema synchronized\n");
}
// Verify build
run("npm run build");
console.log("\nSetup complete! Run 'npm run dev' to start developing.\n");
}
setup();# .env.example (commit this to git)
# Copy this file to .env.local and fill in the values
# Application
VITE_APP_NAME=My Dyad App
VITE_APP_URL=http://localhost:5173
# API Configuration
VITE_API_BASE_URL=http://localhost:4000/api
# Authentication (get keys from your OAuth provider)
VITE_OAUTH_CLIENT_ID=
VITE_OAUTH_REDIRECT_URI=http://localhost:5173/auth/callback
# Third-Party Services
VITE_GA_MEASUREMENT_ID=
VITE_WEATHER_API_KEY=
# Server-side only (not exposed to browser)
API_SECRET_KEY=
DATABASE_URL=postgresql://user:password@localhost:5432/myappOnce deployed, monitor your application's performance and usage.
// services/monitoring.ts
interface PerformanceMetrics {
fcp: number; // First Contentful Paint
lcp: number; // Largest Contentful Paint
fid: number; // First Input Delay
cls: number; // Cumulative Layout Shift
ttfb: number; // Time to First Byte
}
export function reportWebVitals(): void {
if (typeof window === "undefined") return;
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
const metric = {
name: entry.name,
value: entry.startTime,
id: crypto.randomUUID(),
page: window.location.pathname,
};
// Send to analytics endpoint
if (navigator.sendBeacon) {
navigator.sendBeacon(
"/api/analytics/vitals",
JSON.stringify(metric)
);
}
console.log(`[Web Vital] ${entry.name}: ${entry.startTime.toFixed(2)}ms`);
}
});
observer.observe({ type: "largest-contentful-paint", buffered: true });
observer.observe({ type: "first-input", buffered: true });
observer.observe({ type: "layout-shift", buffered: true });
}
export function reportError(error: Error, context?: Record<string, unknown>): void {
const payload = {
message: error.message,
stack: error.stack,
url: window.location.href,
userAgent: navigator.userAgent,
timestamp: new Date().toISOString(),
context,
};
fetch("/api/analytics/errors", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
}).catch(() => {
// Silently fail if error reporting itself fails
});
}
// Global error handler
window.addEventListener("error", (event) => {
reportError(event.error ?? new Error(event.message));
});
window.addEventListener("unhandledrejection", (event) => {
reportError(
event.reason instanceof Error
? event.reason
: new Error(String(event.reason))
);
});| Metric | Good | Needs Improvement | Poor |
|---|---|---|---|
| First Contentful Paint | < 1.8s | 1.8s - 3.0s | > 3.0s |
| Largest Contentful Paint | < 2.5s | 2.5s - 4.0s | > 4.0s |
| First Input Delay | < 100ms | 100ms - 300ms | > 300ms |
| Cumulative Layout Shift | < 0.1 | 0.1 - 0.25 | > 0.25 |
| Time to First Byte | < 200ms | 200ms - 500ms | > 500ms |
| Total Bundle Size (gzipped) | < 200KB | 200KB - 500KB | > 500KB |
Before considering your deployment complete, verify the following:
| Category | Check | Status |
|---|---|---|
| Build | Production build succeeds without warnings | Required |
| Build | Bundle size within performance budget | Required |
| Security | Environment variables are set (not hardcoded) | Required |
| Security | Security headers configured (CSP, X-Frame-Options) | Required |
| Security | HTTPS enabled with valid SSL certificate | Required |
| Performance | Assets served with cache headers | Recommended |
| Performance | Gzip or Brotli compression enabled | Recommended |
| Performance | Images optimized and lazy-loaded | Recommended |
| Routing | SPA fallback configured (serves index.html for all routes) | Required |
| Monitoring | Error tracking configured | Recommended |
| Monitoring | Web Vitals reporting active | Recommended |
| SEO | Meta tags and Open Graph tags present | Recommended |
| Accessibility | Lighthouse accessibility score > 90 | Recommended |
Deploying your Dyad application is the final step in the journey from idea to live product. In this chapter, you learned how to create optimized production builds with Vite, deploy to popular platforms including Vercel, Netlify, and self-hosted Docker containers, automate your pipeline with GitHub Actions CI/CD, configure custom domains with SSL, set up collaboration workflows for team members, and implement production monitoring with Web Vitals and error tracking. Each hosting option offers different trade-offs between simplicity, control, and cost -- choose the one that best fits your project's needs.
- Build optimization -- Vite's tree shaking, code splitting, and minification produce small, fast production bundles out of the box.
- Platform choice matters -- Vercel and Netlify provide zero-config deployments for most Dyad apps; Docker gives full control for complex architectures.
- Automate everything -- GitHub Actions CI/CD pipelines ensure every change is linted, tested, built, and deployed automatically.
- Security by default -- Configure security headers, use HTTPS, and keep secrets in environment variables, never in source code.
- Monitor in production -- Web Vitals tracking and error reporting help you catch issues before users report them.
- Share with intention -- Provide
.env.examplefiles, setup scripts, and clear documentation to make collaboration seamless.
You have completed the Dyad tutorial. You now have the knowledge and tools to build full-stack web applications using natural language, from initial prompt to production deployment. Here is a recap of everything you have learned across all eight chapters:
- Getting Started -- Installing Dyad and creating your first AI-generated application
- Natural Language Building -- Crafting effective prompts for sophisticated applications
- Component Integration -- Adding UI components and extending functionality
- Data Management -- Connecting databases and managing application data
- API Integration -- Connecting to external services with typed API clients
- Customization and Styling -- Building theme systems, responsive layouts, and animations
- Testing and Validation -- Ensuring reliability with unit, integration, and E2E tests
- Deployment and Sharing -- Taking your application live and collaborating with others
Happy building with Dyad!
Built with insights from the Dyad project.
Most teams struggle here because the hard part is not writing more code, but deciding clear boundaries for build, name, fill so behavior stays predictable as complexity grows.
In practical terms, this chapter helps you avoid three common failures:
- coupling core logic too tightly to one implementation path
- missing the handoff boundaries between setup, execution, and validation
- shipping changes without clear rollback or observability strategy
After working through this chapter, you should be able to reason about Chapter 8: Deployment and Sharing as an operating subsystem inside Dyad Tutorial: Local-First AI App Building, with explicit contracts for inputs, state transitions, and outputs.
Use the implementation notes around uses, classDef, stroke as your checklist when adapting these patterns to your own repository.
Under the hood, Chapter 8: Deployment and Sharing usually follows a repeatable control path:
- Context bootstrap: initialize runtime config and prerequisites for
build. - Input normalization: shape incoming data so
namereceives stable contracts. - Core execution: run the main logic branch and propagate intermediate state through
fill. - Policy and safety checks: enforce limits, auth scopes, and failure boundaries.
- Output composition: return canonical result payloads for downstream consumers.
- Operational telemetry: emit logs/metrics needed for debugging and performance tuning.
When debugging, walk this sequence in order and confirm each stage has explicit success/failure conditions.
Use the following upstream sources to verify implementation details while reading this chapter:
- Dyad README
Why it matters: authoritative reference on
Dyad README(github.com). - Dyad Releases
Why it matters: authoritative reference on
Dyad Releases(github.com). - Dyad Repository
Why it matters: authoritative reference on
Dyad Repository(github.com).
Suggested trace strategy:
- search upstream code for
buildandnameto map concrete implementation paths - compare docs claims against actual runtime/config code before reusing patterns in production