Skip to content

Deploying an Aurelia App

Aurelia is a modern, open-source front-end framework for building browser-based applications with powerful data-binding, a modular architecture, and a strong focus on web standards. It provides developers with the tools to build scalable, maintainable single-page applications (SPAs) with intuitive APIs and comprehensive testing support. Aurelia’s component-driven architecture and excellent documentation make it ideal for large-scale applications requiring deep customization.

This comprehensive guide walks through deploying an Aurelia application 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 Aurelia project, build components and services, configure environment variables, implement security best practices, set up monitoring, deploy custom domains, and troubleshoot common issues. By the end of this guide, you’ll have a production-ready Aurelia application running on Klutch.sh’s global infrastructure with automatic HTTPS and optimized performance.

Prerequisites

  • Node.js & npm (version 18+) – Download Node.js
  • Aurelia CLI installed globally (install with npm install -g aurelia-cli)
  • 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 knowledge of JavaScript/TypeScript, HTML, CSS, and the Node.js ecosystem

Getting Started: Create an Aurelia App

1. Install Aurelia CLI

Install the Aurelia CLI globally:

Terminal window
npm install -g aurelia-cli

2. Create a New Aurelia Project

Create a new Aurelia application:

Terminal window
au new my-aurelia-app
cd my-aurelia-app
npm install

The CLI will guide you through configuration options for project setup.

3. Project Structure

A typical Aurelia project structure looks like:

my-aurelia-app/
├── src/
│ ├── resources/
│ │ ├── attributes/
│ │ ├── binding-behaviors/
│ │ ├── components/
│ │ └── value-converters/
│ ├── services/
│ │ ├── api.ts
│ │ ├── auth.ts
│ │ └── ...
│ ├── app.html
│ ├── app.ts
│ ├── main.ts
│ └── ...
├── aurelia.json
├── package.json
├── tsconfig.json
├── Dockerfile
└── README.md

4. Run the Development Server

Start the Aurelia development server:

Terminal window
au run --watch

Navigate to http://localhost:8080 in your browser. The app will automatically reload as you make changes.

5. Sample Component

Create a new component:

src/resources/components/article-list.ts
import { useResource } from "aurelia";
import { ArticleService } from "../../services/article-service";
export interface Article {
id: string;
title: string;
content: string;
author: string;
createdAt: string;
}
export class ArticleListComponent {
articles: Article[] = [];
loading = true;
error = null;
constructor(private articleService: ArticleService) {}
async attached() {
try {
this.articles = await this.articleService.getArticles();
} catch (err) {
this.error = err;
} finally {
this.loading = false;
}
}
}

6. Sample Component Template

Create the corresponding template:

src/resources/components/article-list.html
<template>
<div class="article-list">
<h2>Articles</h2>
<div if.bind="loading" class="loading">
Loading articles...
</div>
<div if.bind="error" class="error">
Failed to load articles
</div>
<div if.bind="!loading && articles.length === 0" class="no-articles">
No articles found
</div>
<div repeat.for="article of articles" class="article-card">
<h3>${article.title}</h3>
<p>${article.content}</p>
<small>By ${article.author} - ${article.createdAt | dateFormat}</small>
</div>
</div>
</template>

7. Sample Service

Create an API service:

src/services/article-service.ts
import { HttpClient } from "@aurelia/fetch-client";
import { IContainer, injection } from "aurelia";
export interface Article {
id: string;
title: string;
content: string;
author: string;
createdAt: string;
}
@injection(HttpClient)
export class ArticleService {
private apiUrl = import.meta.env.VITE_API_URL || "https://api.example.com";
constructor(private http: HttpClient) {}
async getArticles(): Promise<Article[]> {
const response = await this.http.fetch(`${this.apiUrl}/articles`);
if (!response.ok) {
throw new Error(`Failed to fetch articles: ${response.statusText}`);
}
return await response.json();
}
async getArticle(id: string): Promise<Article> {
const response = await this.http.fetch(`${this.apiUrl}/articles/${id}`);
if (!response.ok) {
throw new Error(`Failed to fetch article: ${response.statusText}`);
}
return await response.json();
}
async createArticle(data: Omit<Article, "id" | "createdAt">): Promise<Article> {
const response = await this.http.fetch(`${this.apiUrl}/articles`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error(`Failed to create article: ${response.statusText}`);
}
return await response.json();
}
async updateArticle(id: string, data: Partial<Article>): Promise<Article> {
const response = await this.http.fetch(`${this.apiUrl}/articles/${id}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error(`Failed to update article: ${response.statusText}`);
}
return await response.json();
}
async deleteArticle(id: string): Promise<void> {
const response = await this.http.fetch(`${this.apiUrl}/articles/${id}`, {
method: "DELETE",
});
if (!response.ok) {
throw new Error(`Failed to delete article: ${response.statusText}`);
}
}
}

8. Root Component

Update your root component:

src/app.ts
export class App {
message = "Welcome to Aurelia on Klutch.sh";
}
src/app.html
<template>
<div class="container">
<header>
<h1>Aurelia Application</h1>
<p>${message}</p>
</header>
<main>
<nav>
<ul>
<li><a href="#home">Home</a></li>
<li><a href="#articles">Articles</a></li>
<li><a href="#about">About</a></li>
</ul>
</nav>
<router-outlet></router-outlet>
</main>
<footer>
<p>&copy; 2024 My Aurelia App. All rights reserved.</p>
</footer>
</div>
</template>

9. Configure Environment Variables

Create a .env file for development:

Terminal window
VITE_API_URL=http://localhost:3000/api
VITE_SITE_NAME=My Aurelia App

Access these in your Aurelia app:

const apiUrl = import.meta.env.VITE_API_URL;
const siteName = import.meta.env.VITE_SITE_NAME;

Local Production Build Test

Before deploying, test the production build locally:

Terminal window
au build --env prod
au run

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


Deploying with Nixpacks

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

Prerequisites for Nixpacks Deployment

  • Your Aurelia 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 Aurelia 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 Aurelia 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 Aurelia 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 Aurelia (a web framework serving HTML/assets).

  7. Set the Internal Port

    Set the internal port to 8080 – this is the port where the Aurelia dev server listens and should be used for Node.js-based production servers.

  8. Add Environment Variables (Optional)

    Add any environment variables your Aurelia app requires:

    NODE_ENV=production
    VITE_API_URL=https://api.example.com
    VITE_SITE_NAME=My Aurelia App

    If you need to customize the Nixpacks build or start command, use these environment variables:

    • BUILD_COMMAND: Override the default build command (e.g., npm run build)
    • START_COMMAND: Override the default start command for serving the built app
  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 Aurelia 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 Aurelia

Create a Dockerfile in the root of your Aurelia project:

# === Build stage ===
FROM node:20-alpine AS builder
WORKDIR /app
RUN npm install -g aurelia-cli
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# === Runtime stage ===
FROM node:20-alpine AS runtime
WORKDIR /app
RUN npm install -g serve
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package*.json ./
ENV NODE_ENV=production
ENV PORT=8080
EXPOSE 8080
CMD ["serve", "-s", "dist", "-l", "8080"]

Dockerfile Notes

  • Builder stage: Installs Aurelia CLI and builds your Aurelia app for production into the dist folder.
  • Runtime stage: Uses a lightweight Node.js Alpine image with the serve utility to serve your compiled Aurelia application.
  • Port: The PORT environment variable is set to 8080, which is the recommended internal port for Aurelia.
  • Multi-stage build: Reduces final image size by excluding build tools and dev dependencies from the runtime container.

Alternative Dockerfile for Static Hosting (Nginx)

If you prefer to serve your Aurelia app with Nginx:

# === Build stage ===
FROM node:20-alpine AS builder
WORKDIR /app
RUN npm install -g aurelia-cli
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# === Runtime stage (Nginx) ===
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Note: With the Nginx Dockerfile, set the internal port to 80 instead of 8080.

Steps to Deploy with Docker

  1. Create a Dockerfile

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

  2. Test Locally (Optional)

    Build and test the Docker image locally:

    Terminal window
    docker build -t aurelia-app:latest .
    docker run -p 8080:8080 aurelia-app:latest

    Visit http://localhost:8080 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 8080 (or 80 if using the Nginx Dockerfile)
    • 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.


Environment Variables

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

NODE_ENV=production
VITE_API_URL=https://api.example.com
VITE_SITE_NAME=My Aurelia App
VITE_ANALYTICS_KEY=your-analytics-key

Accessing Environment Variables in Aurelia

Access environment variables in your Aurelia components and services using import.meta.env:

const apiUrl = import.meta.env.VITE_API_URL;
const siteName = import.meta.env.VITE_SITE_NAME;
// In templates
export class MyComponent {
apiUrl = import.meta.env.VITE_API_URL;
}

Only variables prefixed with VITE_ are exposed to the browser. Keep sensitive data like API keys in server-side only or use server endpoints.


Persistent Storage

If your Aurelia application needs to store files or user-generated content, you can use persistent volumes in Klutch.sh.

Adding Persistent Volumes

  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/uploads).
  4. Set the size (e.g., 1 GiB, 10 GiB).
  5. Save and redeploy your app.

Example: Using Persistent Storage with a Backend Service

If you’re using a backend API for file uploads:

src/services/upload-service.ts
@injection(HttpClient)
export class UploadService {
private apiUrl = import.meta.env.VITE_API_URL;
constructor(private http: HttpClient) {}
async uploadFile(file: File): Promise<{ path: string }> {
const formData = new FormData();
formData.append("file", file);
const response = await this.http.fetch(`${this.apiUrl}/upload`, {
method: "POST",
body: formData,
});
if (!response.ok) {
throw new Error("File upload failed");
}
return await response.json();
}
}

Security Best Practices

1. HTTPS/SSL Enforcement

Klutch.sh automatically provides HTTPS for all deployed apps. Ensure your Aurelia app redirects HTTP to HTTPS by configuring your server appropriately.

2. Content Security Policy

Implement CSP headers in your backend or server configuration:

Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';

3. Environment Variable Protection

Never commit sensitive data to version control. Use Klutch.sh environment variables for:

VITE_API_KEY=your-secret-key
VITE_DATABASE_URL=postgresql://...

Only expose variables prefixed with VITE_ to the browser.

4. Input Validation

Validate all user inputs in your Aurelia components:

export class FormComponent {
name: string = "";
email: string = "";
onSubmit() {
if (!this.name || !this.email) {
return; // Validation failed
}
// Validate email format
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(this.email)) {
return; // Invalid email
}
// Submit form
}
}

5. CORS Configuration

Configure CORS in your backend API if needed:

Access-Control-Allow-Origin: https://example-app.klutch.sh, https://custom-domain.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization

6. Dependency Security

Regularly audit and update dependencies:

Terminal window
npm audit
npm audit fix
npm update

7. Secure API Communication

Use HTTPS for all API calls and implement proper authentication:

@injection(HttpClient)
export class ApiService {
constructor(private http: HttpClient) {
// Configure default headers for authentication
this.http.configure(config => {
config.withDefaults({
headers: {
"Authorization": `Bearer ${this.getToken()}`,
}
});
});
}
private getToken(): string {
return localStorage.getItem("authToken") || "";
}
}

Monitoring and Logging

Health Check Endpoint

Implement a health check endpoint in your backend API:

// Backend example
app.get("/health", (req, res) => {
res.json({
status: "healthy",
timestamp: new Date().toISOString(),
uptime: process.uptime(),
});
});

Client-Side Error Handling

Implement comprehensive error handling in your Aurelia app:

@injection(LoggerService)
export class GlobalErrorHandler {
constructor(private logger: LoggerService) {
window.addEventListener("error", (event) => {
this.logger.error("Global error", {
message: event.message,
filename: event.filename,
lineno: event.lineno,
});
});
window.addEventListener("unhandledrejection", (event) => {
this.logger.error("Unhandled promise rejection", {
reason: event.reason,
});
});
}
}

Structured Logging

Implement structured logging for production monitoring:

export class LoggerService {
log(level: string, message: string, data?: any) {
const entry = {
timestamp: new Date().toISOString(),
level,
message,
data,
};
console.log(JSON.stringify(entry));
}
info(message: string, data?: any) {
this.log("info", message, data);
}
warn(message: string, data?: any) {
this.log("warn", message, data);
}
error(message: string, data?: any) {
this.log("error", message, data);
}
}

Custom Domains

To use a custom domain with your Klutch.sh-deployed Aurelia 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 Aurelia app will be accessible at your custom domain with automatic HTTPS.


Troubleshooting

Issue 1: Build Fails with Memory Error

Error: npm run build fails with “JavaScript heap out of memory”

Solutions:

  • Increase Node memory: NODE_OPTIONS=--max-old-space-size=4096 npm run build
  • Set BUILD_COMMAND in Klutch.sh: NODE_OPTIONS=--max-old-space-size=4096 npm run build
  • Optimize your Aurelia code and reduce bundle size
  • Review large dependencies and consider alternatives

Issue 2: Port Already in Use

Error: EADDRINUSE: address already in use :::8080

Solutions:

  • Ensure the internal port in Klutch.sh matches your configuration
  • Kill the process using the port: lsof -ti:8080 | xargs kill -9
  • Change the port in your Dockerfile or Aurelia configuration

Issue 3: Environment Variables Not Loading

Error: Environment variables are undefined in your app

Solutions:

  • Ensure variables are prefixed with VITE_ for browser access
  • Check that environment variables are set in the Klutch.sh dashboard
  • Rebuild and redeploy after adding environment variables
  • Verify variable names match exactly (case-sensitive)

Issue 4: Assets Return 404

Error: CSS, images, or JavaScript not loading in production

Solutions:

  • Verify base URL is correct in your aurelia.json configuration
  • Ensure all assets are in the public/ or src/ folders
  • Check that asset paths are referenced correctly
  • Clear browser cache and redeploy

Issue 5: Slow Build Times

Error: Build takes longer than expected

Solutions:

  • Check for large dependencies or unnecessary imports
  • Use lazy loading for heavy modules
  • Optimize images before including them
  • Set BUILD_COMMAND if using custom build steps

Best Practices

1. Use Value Converters

Create reusable value converters for data transformation:

src/resources/value-converters/date-format.ts
export class DateFormatValueConverter {
toView(value: Date | string, format: string = "short"): string {
const date = new Date(value);
if (format === "short") {
return date.toLocaleDateString();
}
return date.toLocaleString();
}
}

2. Implement Binding Behaviors

Use binding behaviors for advanced control:

src/resources/binding-behaviors/debounce.ts
export class DebounceBindingBehavior {
bind(binding: Binding, source: any, delay: number = 300) {
let timeout;
const originalUpdateTarget = binding.updateTarget;
binding.updateTarget = function(value) {
clearTimeout(timeout);
timeout = setTimeout(() => {
originalUpdateTarget.call(binding, value);
}, delay);
};
}
}

3. Use Composition for Component Reuse

Compose complex UIs from simple, reusable components:

src/app.html
<template>
<compose view-model.bind="router.currentInstruction.config.moduleId">
</compose>
</template>

4. Implement Proper Routing

Set up clean, semantic routing:

src/app.ts
export class App {
router: Router;
configureRouter(config: RouterConfiguration, router: Router) {
config.title = "Aurelia App";
config.map([
{ route: ["", "home"], name: "home", component: Home, title: "Home" },
{ route: "articles", name: "articles", component: Articles, title: "Articles" },
{ route: "articles/:id", name: "article-detail", component: ArticleDetail, title: "Article" },
{ route: "about", name: "about", component: About, title: "About" },
]);
this.router = router;
}
}

5. Use Dependency Injection

Leverage Aurelia’s powerful DI container:

@injection(HttpClient, AuthService, LoggerService)
export class MyComponent {
constructor(
private http: HttpClient,
private auth: AuthService,
private logger: LoggerService
) {}
}

6. Optimize Performance with Lazy Loading

Load routes and modules on demand:

config.map([
{
route: "admin",
name: "admin",
moduleId: "admin/index",
nav: true,
title: "Admin",
settings: { roles: ["admin"] },
},
]);

7. Implement Proper Error Boundaries

Handle errors gracefully throughout your app:

export class ErrorBoundary {
error: Error | null = null;
bind(bindingContext: any) {
this.error = null;
}
@bound
catch(error: Error) {
this.error = error;
return true; // Prevents error propagation
}
}

8. Keep Dependencies Updated

Regularly update Aurelia and related packages:

Terminal window
npm update aurelia framework
npm audit fix

9. Test Your Components

Write tests for your Aurelia components:

describe("ArticleListComponent", () => {
let component: ArticleListComponent;
beforeEach(() => {
const mockService = {
getArticles: () => Promise.resolve([]),
};
component = new ArticleListComponent(mockService as any);
});
it("should load articles on attach", async () => {
await component.attached();
expect(component.loading).toBe(false);
});
});

10. Document Your Code

Maintain clear documentation for components and services:

/**
* ArticleService - Handles all article-related API calls
*
* Methods:
* - getArticles(): Retrieves all articles
* - getArticle(id): Retrieves a single article by ID
* - createArticle(data): Creates a new article
* - updateArticle(id, data): Updates an existing article
* - deleteArticle(id): Deletes an article
*/
@injection(HttpClient)
export class ArticleService {
// ...
}

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 components render correctly.
  3. Check Performance: Use Google PageSpeed Insights to verify performance.
  4. Test API Integration: If your app calls APIs, verify the requests work correctly.
  5. Review Console: Open the browser console (F12) and verify no errors are present.
  6. Check Network: Use browser DevTools to verify all assets load correctly.
  7. Review Logs: Check the Klutch.sh dashboard logs for any runtime 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 Aurelia application 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 Aurelia project, create components and services, configure environment variables, implement security best practices, set up monitoring, and troubleshoot common issues. Your Aurelia application is now running on Klutch.sh’s global infrastructure with automatic HTTPS, custom domain support, and production-grade performance. For additional help or questions, consult the official Aurelia documentation or contact Klutch.sh support.