Skip to content

Deploying an Analog App

Analog is a full-stack meta-framework for Angular that combines file-based routing, server-side rendering (SSR), static-site generation, and API routes on top of Vite. Built on modern Angular and featuring seamless TypeScript support, Analog enables developers to build fast, SEO-friendly applications with an exceptional developer experience.

This comprehensive guide walks through deploying an Analog app to Klutch.sh using either Nixpacks (automatic zero-configuration deployment) or a Dockerfile (manual container control). You’ll learn how to create and structure an Analog project, add sample code and API routes, configure environment variables, implement security best practices, set up monitoring, and troubleshoot common deployment issues. By the end of this guide, you’ll have a production-ready Analog application running on Klutch.sh’s global infrastructure.

Prerequisites

  • Node.js & npm (version 18+) – Download Node.js
  • Git installed locally and a GitHub account (Klutch.sh uses GitHub as the only git source)
  • Klutch.sh account with access to the dashboard at klutch.sh/app
  • Basic understanding of Analog, Angular, TypeScript, and the Node.js ecosystem

Getting Started: Create an Analog App

You can create a new Analog app using the official scaffolding tool or manually configure an existing Angular project. Below is the recommended approach for a fresh Analog project.

1. Scaffold a New Analog Project

In an empty directory, use the official Analog starter:

Terminal window
npm init @analogjs/app@latest my-analog-app
cd my-analog-app
npm install

This command creates a fully configured Analog project with pre-set scripts, routing, and Vite configuration.

2. Project Structure

A typical Analog project structure looks like:

my-analog-app/
├── src/
│ ├── app/
│ │ ├── pages/
│ │ │ ├── index.page.ts
│ │ │ ├── about.page.ts
│ │ │ └── ...
│ │ ├── api/
│ │ │ ├── articles/
│ │ │ │ ├── [id].ts
│ │ │ │ └── index.ts
│ │ │ └── ...
│ │ └── ...
│ ├── main.ts
│ ├── main.server.ts
│ └── styles.css
├── package.json
├── vite.config.ts
├── Dockerfile
└── README.md

3. Run the Development Server

Start the Analog development server:

Terminal window
npm run dev

Navigate to http://localhost:5173 in your browser. Hot module replacement (HMR) is enabled by default.

4. Sample Page Component

Create a simple home page to verify local development:

src/app/pages/index.page.ts
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-home-page',
standalone: true,
imports: [CommonModule],
template: `
<main class="home-container">
<h1>Welcome to Analog</h1>
<p>This Analog app is running on Klutch.sh.</p>
<section>
<h2>Features</h2>
<ul>
<li>File-based routing with dynamic routes</li>
<li>Server-side rendering (SSR) for SEO</li>
<li>API routes for backend functionality</li>
<li>Full TypeScript support</li>
</ul>
</section>
</main>
`,
styles: [
`
.home-container {
max-width: 800px;
margin: 0 auto;
padding: 2rem;
font-family: system-ui, -apple-system, sans-serif;
}
`,
],
})
export default class HomePage {}

5. Sample API Route

Create an API endpoint that returns JSON data:

src/app/api/articles/index.ts
import { defineEventHandler, readBody } from 'h3';
interface Article {
id: number;
title: string;
content: string;
}
const articles: Article[] = [
{
id: 1,
title: 'Getting Started with Analog',
content: 'Learn the basics of Analog framework.',
},
{
id: 2,
title: 'SSR with Analog',
content: 'Build server-rendered Angular applications.',
},
];
export default defineEventHandler(async (event) => {
if (event.node.req.method === 'GET') {
return articles;
}
if (event.node.req.method === 'POST') {
const body = await readBody(event);
const newArticle: Article = {
id: articles.length + 1,
...body,
};
articles.push(newArticle);
return newArticle;
}
return { error: 'Method not allowed' };
});

6. Dynamic API Route

Create a dynamic route for fetching a single article:

src/app/api/articles/[id].ts
import { defineEventHandler } from 'h3';
interface Article {
id: number;
title: string;
content: string;
}
const articles: Article[] = [
{
id: 1,
title: 'Getting Started with Analog',
content: 'Learn the basics of Analog framework.',
},
{
id: 2,
title: 'SSR with Analog',
content: 'Build server-rendered Angular applications.',
},
];
export default defineEventHandler((event) => {
const id = parseInt(event.context.params?.id as string, 10);
const article = articles.find((a) => a.id === id);
if (!article) {
event.node.res.statusCode = 404;
return { error: 'Article not found' };
}
return article;
});

7. Production Build Scripts

Ensure your package.json includes the necessary production scripts:

{
"scripts": {
"dev": "analog dev",
"build": "analog build",
"start": "analog start",
"preview": "vite preview",
"type-check": "tsc --noEmit"
},
"dependencies": {
"@analogjs/core": "^1.0.0",
"@angular/animations": "^17.0.0",
"@angular/common": "^17.0.0",
"@angular/compiler": "^17.0.0",
"@angular/core": "^17.0.0",
"@angular/platform-browser": "^17.0.0",
"@angular/platform-browser-dynamic": "^17.0.0",
"express": "^4.18.0",
"vite": "^5.0.0",
"zone.js": "^0.14.0"
},
"devDependencies": {
"@angular/compiler-cli": "^17.0.0",
"@types/node": "^20.0.0",
"typescript": "^5.2.0"
}
}

Local Production Test

Before deploying, test the production build locally:

Terminal window
npm run build
npm run start

Visit http://localhost:3000 to verify that your app renders correctly in production mode.


Deploying with Nixpacks

Nixpacks automatically detects your Node.js application and configures build and runtime environments without requiring a Dockerfile. This is the simplest deployment method for Analog apps.

Prerequisites for Nixpacks Deployment

  • Your Analog project pushed to a GitHub repository
  • Valid package.json with build and start scripts
  • No Dockerfile in the repository root (if one exists, Klutch.sh will use Docker instead)

Steps to Deploy with Nixpacks

  1. Push Your Analog Project to GitHub

    Initialize and push your project to GitHub if you haven’t already:

    Terminal window
    git init
    git add .
    git commit -m "Initial Analog app"
    git branch -M main
    git remote add origin git@github.com:YOUR_USERNAME/YOUR_REPO.git
    git push -u origin main
  2. Log In to Klutch.sh Dashboard

    Go to klutch.sh/app and sign in with your GitHub account.

  3. Create a Project

    Navigate to the Projects section and create a new project for your Analog app.

  4. Create an App

    Click “Create App” and select your GitHub repository.

  5. Select the Branch

    Choose the branch you want to deploy (typically main).

  6. Configure Traffic Type

    Select HTTP as the traffic type for Analog (a web framework serving HTML/JSON).

  7. Set the Internal Port

    Set the internal port to 3000 – this is the port where your Analog server listens internally.

  8. Add Environment Variables (Optional)

    Add any environment variables your Analog app requires:

    NODE_ENV=production
    API_URL=https://api.example.com
    LOG_LEVEL=info

    If you need to customize the Nixpacks build or start command:

    • BUILD_COMMAND: Override the default build command (e.g., npm run build && npm run build:server)
    • START_COMMAND: Override the default start command (e.g., node dist/server.js)
  9. Configure Compute Resources

    Select your region, compute size, and number of instances based on expected traffic.

  10. Deploy

    Click “Create” to start the deployment. Nixpacks will automatically build and deploy your Analog app. Your app will be available at a URL like https://example-app.klutch.sh.


Deploying with Docker

For more control over your deployment environment, you can use a Dockerfile. Klutch.sh automatically detects a Dockerfile in your repository root and uses it for deployment.

Creating a Dockerfile for Analog

Create a Dockerfile in the root of your Analog project:

# === Build stage ===
FROM node:20-alpine AS builder
WORKDIR /app
# Install dependencies
COPY package*.json ./
RUN npm install
# Copy the rest of the source code
COPY . .
# Build the Analog app for production
RUN npm run build
# === Runtime stage ===
FROM node:20-alpine AS runtime
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install production dependencies only
RUN npm install --omit=dev
# Copy built assets and compiled code from builder
COPY --from=builder /app/dist ./dist
# Set the port for Analog to listen on
ENV PORT=3000
EXPOSE 3000
# Start the Analog server in production mode
CMD ["npm", "run", "start"]

Dockerfile Notes

  • Builder stage: Installs all dependencies (including dev) and builds the Analog app.
  • Runtime stage: Contains only production dependencies and the compiled app, resulting in a smaller, more secure image.
  • Port: The PORT environment variable is set to 3000, which is the recommended internal port for Analog.
  • Multi-stage build: Reduces final image size by excluding build tools and dev dependencies.

Steps to Deploy with Docker

  1. Create a Dockerfile

    Add the Dockerfile (shown above) to the root of your Analog repository.

  2. Test Locally (Optional)

    Build and test the Docker image locally:

    Terminal window
    docker build -t analog-app:latest .
    docker run -p 3000:3000 analog-app:latest

    Visit http://localhost:3000 to verify.

  3. Push to GitHub

    Commit and push the Dockerfile and your code:

    Terminal window
    git add Dockerfile
    git commit -m "Add Dockerfile for production deployment"
    git push origin main
  4. Create an App in Klutch.sh

    Go to klutch.sh/app, navigate to “Create App”, and select your repository.

  5. Configure the App
    • Traffic Type: Select HTTP
    • Internal Port: Set to 3000
    • Environment Variables: Add any required runtime variables
  6. Deploy

    Klutch.sh automatically detects the Dockerfile and uses it to build and deploy your app. Your app will be available at https://example-app.klutch.sh.


Database Configuration

Many Analog applications require a database for persisting data. Klutch.sh supports integration with PostgreSQL and MySQL.

Environment Variables for Database Connection

Set these environment variables in the Klutch.sh dashboard:

DATABASE_URL=postgresql://user:password@host:5432/dbname
# or
DATABASE_URL=mysql://user:password@host:3306/dbname

Connecting to a Database Service

If you’re running a separate database service on Klutch.sh:

  1. Deploy a PostgreSQL or MySQL service on Klutch.sh.
  2. In your Analog app, retrieve the database connection details from Klutch.sh (usually provided as an environment variable).
  3. Update your connection configuration in your Analog app to use the provided DATABASE_URL.

Sample Database Integration

Create a service to handle database operations:

src/app/services/article.service.ts
import { Injectable } from '@angular/core';
interface Article {
id: number;
title: string;
content: string;
author: string;
created_at: string;
}
@Injectable({
providedIn: 'root',
})
export class ArticleService {
private dbUrl = process.env['DATABASE_URL'];
constructor() {
console.log('Database URL configured:', this.dbUrl ? 'Yes' : 'No');
}
async getArticles(): Promise<Article[]> {
// Example: Use database client (e.g., node-postgres, mysql2)
// This is a placeholder for your actual database logic
const articles: Article[] = [
{
id: 1,
title: 'Analog on Klutch.sh',
content: 'Deploy full-stack Angular apps with ease.',
author: 'Jane Doe',
created_at: new Date().toISOString(),
},
];
return articles;
}
async getArticleById(id: number): Promise<Article | null> {
const articles = await this.getArticles();
return articles.find((a) => a.id === id) || null;
}
}

Environment Variables

Define all environment variables in the Klutch.sh dashboard. Here’s a recommended set for production:

NODE_ENV=production
PORT=3000
LOG_LEVEL=info
API_URL=https://api.example.com
DATABASE_URL=postgresql://user:password@host:5432/dbname
REDIS_URL=redis://host:6379
SESSION_SECRET=your-secure-random-secret
DEBUG=false
RATE_LIMIT_ENABLED=true

Accessing Environment Variables in Analog

Access environment variables in both server and client code:

// Server-side (API routes)
const nodeEnv = process.env['NODE_ENV'];
const databaseUrl = process.env['DATABASE_URL'];
// Client-side (with caution – only public variables)
declare const process: any;
const apiUrl = import.meta.env['VITE_API_URL'];

Persistent Storage

If your Analog app needs to store uploaded files or other persistent data, attach a volume in Klutch.sh.

Adding a Persistent Volume

  1. In the Klutch.sh dashboard, go to your app’s Volumes section.
  2. Click Add Volume.
  3. Set the mount path (e.g., /data, /uploads, or /var/www/data).
  4. Set the size (e.g., 1 GiB, 10 GiB).
  5. Save and redeploy your app.

Using Persistent Storage in Analog

Update your API routes to write files to the mounted directory:

src/app/api/upload.ts
import { defineEventHandler, readMultipartFormData } from 'h3';
import { writeFile } from 'fs/promises';
import { join } from 'path';
export default defineEventHandler(async (event) => {
const formData = await readMultipartFormData(event);
const file = formData?.find((f) => f.name === 'file');
if (!file) {
return { error: 'No file provided' };
}
const uploadsDir = '/data/uploads'; // Mount path
const fileName = `${Date.now()}-${file.filename}`;
const filePath = join(uploadsDir, fileName);
await writeFile(filePath, file.data);
return {
success: true,
fileName,
path: `/uploads/${fileName}`,
};
});

Caching with Redis

Improve performance by caching frequently accessed data with Redis.

Environment Configuration

Set the Redis connection URL in the Klutch.sh dashboard:

REDIS_URL=redis://host:6379

Redis Integration Example

Create a caching utility:

src/app/utils/cache.ts
import * as redis from 'redis';
let redisClient: redis.RedisClientType;
export async function initRedis() {
const redisUrl = process.env['REDIS_URL'] || 'redis://localhost:6379';
redisClient = redis.createClient({ url: redisUrl });
redisClient.on('error', (err) => console.error('Redis error:', err));
await redisClient.connect();
}
export async function getCached<T>(key: string): Promise<T | null> {
const cached = await redisClient.get(key);
return cached ? JSON.parse(cached) : null;
}
export async function setCached<T>(key: string, value: T, ttl = 3600) {
await redisClient.setEx(key, ttl, JSON.stringify(value));
}
export async function deleteCached(key: string) {
await redisClient.del(key);
}

Use caching in your API routes:

src/app/api/articles/index.ts
import { defineEventHandler } from 'h3';
import { getCached, setCached } from '../../utils/cache';
const articles = [
{
id: 1,
title: 'Getting Started',
content: 'Learn Analog...',
},
];
export default defineEventHandler(async (event) => {
if (event.node.req.method === 'GET') {
const cached = await getCached('articles');
if (cached) {
return cached;
}
const data = articles;
await setCached('articles', data, 3600); // Cache for 1 hour
return data;
}
return [];
});

Security Best Practices

1. HTTPS/SSL Enforcement

Klutch.sh automatically provides HTTPS for all deployed apps. Ensure your app redirects HTTP to HTTPS:

src/app/middleware/https.ts
import { defineEventHandler } from 'h3';
export default defineEventHandler((event) => {
const protocol = event.node.req.headers['x-forwarded-proto'];
if (protocol === 'http' && process.env['NODE_ENV'] === 'production') {
const url = new URL(event.node.req.url || '/', `https://${event.node.req.headers.host}`);
event.node.res.writeHead(301, { Location: url.toString() });
event.node.res.end();
}
});

2. CORS Configuration

Restrict cross-origin requests to trusted domains:

src/app/middleware/cors.ts
import { defineEventHandler } from 'h3';
export default defineEventHandler((event) => {
const origin = event.node.req.headers['origin'] || '';
const allowedOrigins = [
'https://example-app.klutch.sh',
'https://example.com',
];
if (allowedOrigins.includes(origin)) {
event.node.res.setHeader('Access-Control-Allow-Origin', origin);
event.node.res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
event.node.res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
}
if (event.node.req.method === 'OPTIONS') {
event.node.res.writeHead(200);
event.node.res.end();
}
});

3. Environment Secrets

Never commit sensitive information. Use Klutch.sh’s environment variables for:

  • Database credentials
  • API keys
  • Session secrets
  • JWT signing keys
DATABASE_PASSWORD=your-secure-password
API_KEY=your-api-key
SESSION_SECRET=generate-with-crypto.randomBytes(32)
JWT_SECRET=your-jwt-secret

4. Input Validation

Validate all user inputs in API routes:

src/app/api/articles.ts
import { defineEventHandler, readBody } from 'h3';
export default defineEventHandler(async (event) => {
if (event.node.req.method === 'POST') {
const body = await readBody(event);
// Validate required fields
if (!body.title || !body.content || !body.author) {
event.node.res.statusCode = 400;
return { error: 'Missing required fields' };
}
// Validate field lengths
if (body.title.length > 255 || body.content.length > 10000) {
event.node.res.statusCode = 400;
return { error: 'Field length exceeded' };
}
// Sanitize and process
return { success: true, article: body };
}
});

5. Authentication & Authorization

Implement JWT-based authentication:

src/app/utils/auth.ts
import * as jwt from 'jsonwebtoken';
const SECRET = process.env['JWT_SECRET'] || 'dev-secret';
export function generateToken(userId: string) {
return jwt.sign({ userId }, SECRET, { expiresIn: '24h' });
}
export function verifyToken(token: string) {
try {
return jwt.verify(token, SECRET);
} catch {
return null;
}
}

Use authentication middleware in protected routes:

src/app/api/protected.ts
import { defineEventHandler } from 'h3';
import { verifyToken } from '../utils/auth';
export default defineEventHandler((event) => {
const authHeader = event.node.req.headers['authorization'];
const token = authHeader?.replace('Bearer ', '');
if (!token || !verifyToken(token)) {
event.node.res.statusCode = 401;
return { error: 'Unauthorized' };
}
return { message: 'Access granted', data: 'Protected content' };
});

6. Rate Limiting

Prevent abuse by implementing rate limiting:

src/app/middleware/rate-limit.ts
import { defineEventHandler } from 'h3';
const requests: { [key: string]: number[] } = {};
const LIMIT = 100; // requests per minute
const WINDOW = 60 * 1000; // 1 minute
export default defineEventHandler((event) => {
const ip = event.node.req.headers['x-forwarded-for'] as string || 'unknown';
const now = Date.now();
if (!requests[ip]) {
requests[ip] = [];
}
// Remove old requests outside the window
requests[ip] = requests[ip].filter((time) => now - time < WINDOW);
if (requests[ip].length >= LIMIT) {
event.node.res.statusCode = 429;
return { error: 'Rate limit exceeded' };
}
requests[ip].push(now);
});

7. Structured Logging

Log security events for monitoring:

src/app/utils/logger.ts
export function logSecurityEvent(event: string, details: Record<string, any>) {
const timestamp = new Date().toISOString();
console.log(
JSON.stringify({
timestamp,
event,
level: 'security',
...details,
})
);
}

Monitoring and Logging

Health Check Endpoint

Implement a health check endpoint for monitoring:

src/app/api/health.ts
import { defineEventHandler } from 'h3';
export default defineEventHandler((event) => {
const uptime = process.uptime();
const memoryUsage = process.memoryUsage();
return {
status: 'healthy',
uptime,
timestamp: new Date().toISOString(),
memory: {
rss: Math.round(memoryUsage.rss / 1024 / 1024) + ' MB',
heapUsed: Math.round(memoryUsage.heapUsed / 1024 / 1024) + ' MB',
heapTotal: Math.round(memoryUsage.heapTotal / 1024 / 1024) + ' MB',
},
};
});

Structured Logging

Implement structured logging for better observability:

src/app/utils/logger.ts
export function logRequest(method: string, path: string, status: number, duration: number) {
console.log(
JSON.stringify({
timestamp: new Date().toISOString(),
type: 'http_request',
method,
path,
status,
duration_ms: duration,
})
);
}
export function logError(error: Error, context: string) {
console.error(
JSON.stringify({
timestamp: new Date().toISOString(),
type: 'error',
context,
message: error.message,
stack: error.stack,
})
);
}

Monitoring Key Metrics

Track important metrics in your API routes:

src/app/api/articles/index.ts
import { defineEventHandler } from 'h3';
import { logRequest } from '../../utils/logger';
export default defineEventHandler(async (event) => {
const startTime = Date.now();
try {
const articles = [
{ id: 1, title: 'Article 1', content: 'Content...' },
];
const duration = Date.now() - startTime;
logRequest('GET', '/api/articles', 200, duration);
return articles;
} catch (error) {
const duration = Date.now() - startTime;
logRequest('GET', '/api/articles', 500, duration);
throw error;
}
});

Custom Domains

To use a custom domain with your Klutch.sh-deployed Analog app:

1. Add the Domain in Klutch.sh

In the Klutch.sh dashboard, go to your app’s settings and add your custom domain (e.g., app.example.com).

2. Update Your DNS Provider

Update your DNS records with the CNAME provided by Klutch.sh:

CNAME: app.example.com → example-app.klutch.sh

3. Wait for DNS Propagation

DNS changes can take up to 48 hours to propagate. Use tools to verify:

Terminal window
nslookup app.example.com
# or
dig app.example.com

Once propagated, your Analog app will be accessible at your custom domain with automatic HTTPS.


Troubleshooting

Issue 1: App Fails to Build

Error: npm ERR! ERR! code ENOENT

Solutions:

  • Ensure package.json exists in the repository root.
  • Verify all npm scripts are correctly defined (especially build and start).
  • Check that all required dependencies are listed in package.json.
  • Clear the cache and rebuild: rm -rf node_modules package-lock.json && npm install

Issue 2: Port Binding Error

Error: EADDRINUSE: address already in use :::3000

Solutions:

  • Ensure the internal port in Klutch.sh matches the port your Analog server uses.
  • Check that no other process is using port 3000 locally.
  • Set the PORT environment variable in your app if using a custom port.

Issue 3: Database Connection Fails

Error: connect ECONNREFUSED 127.0.0.1:5432

Solutions:

  • Verify the DATABASE_URL is correctly set in Klutch.sh environment variables.
  • Ensure the database service is running and accessible.
  • Check database credentials and network connectivity.
  • Test the connection locally before deploying.

Issue 4: Module Not Found

Error: Cannot find module '@analogjs/core'

Solutions:

  • Run npm install to ensure all dependencies are installed.
  • Clear the npm cache: npm cache clean --force
  • Delete node_modules and package-lock.json, then reinstall.
  • Verify the package.json includes all required Analog dependencies.

Issue 5: Environment Variables Not Loaded

Error: process.env is undefined or missing values

Solutions:

  • Ensure environment variables are set in the Klutch.sh dashboard.
  • Redeploy the app after adding new environment variables.
  • Use console.log(process.env) in your code to debug (remove before production).
  • For client-side code, use import.meta.env instead of process.env.

Best Practices

1. Use Environment Variables for Configuration

Never hardcode sensitive information or environment-specific values:

// Good
const apiUrl = process.env['API_URL'];
const dbUrl = process.env['DATABASE_URL'];
// Avoid
const apiUrl = 'https://api.hardcoded.com';
const dbUrl = 'postgresql://user:pass@localhost:5432/db';

2. Implement Error Handling

Handle errors gracefully in your API routes and components:

// Good
export default defineEventHandler(async (event) => {
try {
const data = await fetchData();
return data;
} catch (error) {
event.node.res.statusCode = 500;
return { error: 'Internal server error' };
}
});
// Avoid
export default defineEventHandler(async (event) => {
return await fetchData(); // No error handling
});

3. Cache Expensive Operations

Use Redis to cache database queries and API responses:

// Good
const cached = await getCached('articles');
if (cached) return cached;
const data = await queryDatabase();
await setCached('articles', data, 3600);
return data;
// Avoid
return await queryDatabase(); // Every request hits the database

4. Use Compression Middleware

Enable compression for faster data transfer:

src/app/middleware/compress.ts
import { defineEventHandler } from 'h3';
export default defineEventHandler((event) => {
event.node.res.setHeader('Accept-Encoding', 'gzip, deflate, br');
});

5. Optimize Images and Assets

Minimize and optimize static assets:

Terminal window
# Use tools like imagemin for images
npx imagemin src/assets/*.png --out-dir=dist/assets
# Use ESBuild for JavaScript optimization (Vite does this automatically)

6. Monitor Performance

Track response times and error rates:

src/app/middleware/metrics.ts
const metrics = {
requests: 0,
errors: 0,
totalTime: 0,
};
export default defineEventHandler((event) => {
const startTime = Date.now();
event.node.res.on('finish', () => {
metrics.requests++;
metrics.totalTime += Date.now() - startTime;
if (event.node.res.statusCode >= 400) {
metrics.errors++;
}
});
});

7. Keep Dependencies Updated

Regularly update Analog and Angular to the latest versions:

Terminal window
npm outdated
npm update
npm audit fix

8. Use TypeScript Strict Mode

Enable strict type checking to catch errors early:

{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true
}
}

9. Implement Proper Testing

Add unit and integration tests:

Terminal window
ng generate service article
ng generate component article-list
npm test

10. Document Your API

Document your API routes and environment variables:

/**
* GET /api/articles
* Retrieves all articles with optional filtering
*
* Query Parameters:
* - limit: number (default: 10)
* - offset: number (default: 0)
*
* Response: Array<Article>
*/
export default defineEventHandler(async (event) => {
// Implementation
});

Verifying Your Deployment

After deployment completes:

  1. Check the App URL: Visit your app at https://example-app.klutch.sh or your custom domain.

  2. Verify Pages Render: Ensure all pages load and render correctly.

  3. Test API Routes: Test your API endpoints with curl or a REST client:

    Terminal window
    curl https://example-app.klutch.sh/api/articles
    curl -X POST https://example-app.klutch.sh/api/articles \
    -H "Content-Type: application/json" \
    -d '{"title":"New Article","content":"..."}'
  4. Check Health Endpoint: Verify the health check endpoint:

    Terminal window
    curl https://example-app.klutch.sh/api/health
  5. Review Logs: Check the Klutch.sh dashboard for deployment logs and any errors.

If your app doesn’t work as expected, review the troubleshooting section and check the logs in the Klutch.sh dashboard.


External Resources


Deploying an Analog app to Klutch.sh is straightforward with Nixpacks for automatic deployment or Docker for fine-grained control. By following this guide, you’ve learned how to scaffold an Analog project, configure environment variables, implement security best practices, set up monitoring, and troubleshoot common issues. Your Analog application is now running on Klutch.sh’s global infrastructure with automatic HTTPS, custom domain support, and persistent storage capabilities. For additional help or questions, consult the official Analog documentation or contact Klutch.sh support.