Skip to content

Deploying Digiboardi

Digiboardi is a modern digital signage and information board platform that brings dynamic content display to any screen. Built with Node.js on the backend and React on the frontend, it transforms ordinary displays into intelligent information hubs that can show everything from weather forecasts and transit schedules to custom announcements and real-time data feeds. Unlike expensive proprietary digital signage solutions that lock you into specific hardware and charge recurring licensing fees, Digiboardi is open source and runs on any device with a web browser—from Raspberry Pis to recycled laptops to dedicated display hardware.

The platform shines in its flexibility and ease of use. Through a clean web interface, you can create and manage multiple display boards, each with its own set of widgets and content modules. Want to show the current weather alongside your company’s calendar and a live Twitter feed? Digiboardi makes it simple. Need to display rotating announcements with embedded videos and images? That’s built in. The modular widget system means you can customize exactly what information appears on each screen, and the responsive design ensures content looks good whether displayed on a small tablet or a massive lobby screen. Updates happen in real-time, so when you change content in the admin panel, all connected displays reflect those changes immediately without any manual intervention.

Why Deploy Digiboardi on Klutch.sh?

Deploying Digiboardi on Klutch.sh offers several compelling advantages for digital signage:

  • Always-On Reliability: Keep your digital signage server running 24/7 without maintaining local infrastructure
  • Automatic HTTPS: Secure access to your admin panel and display endpoints with built-in SSL certificates
  • Persistent Storage: Your boards, widgets, and media files persist across deployments with volume management
  • Remote Management: Update displays from anywhere without accessing local hardware
  • Easy Scaling: Run multiple display configurations for different locations from a single deployment
  • Zero Hardware Maintenance: Focus on content creation, not server administration
  • Quick Deployment: Go from code to production displays in minutes

Prerequisites

Before deploying Digiboardi to Klutch.sh, ensure you have:

  • A Klutch.sh account (sign up here)
  • A GitHub account with a repository for your Digiboardi deployment
  • Basic understanding of Docker and containerization
  • Familiarity with Node.js and React applications
  • Understanding of digital signage concepts and display management
  • Git installed on your local development machine
  • Display devices (tablets, monitors, Raspberry Pis) that can access web pages

Understanding Digiboardi Architecture

Digiboardi follows a modern web application architecture optimized for real-time content delivery:

Core Components

Backend API Server

The Node.js backend serves as the central hub for all Digiboardi operations. Built with Express.js, it provides a RESTful API that handles board configuration, widget management, user authentication, and content storage. The server maintains the state of all active boards and their associated widgets, persisting data to a SQLite database by default (with support for PostgreSQL in production environments). WebSocket connections enable real-time updates, pushing content changes to connected displays instantly without requiring page refreshes.

Key backend responsibilities:

  • API endpoints for board and widget management
  • User authentication and authorization
  • Database operations and data persistence
  • Media file upload and storage
  • WebSocket server for real-time updates
  • External API integration (weather, transit, etc.)
  • Scheduled content updates and rotations

React Frontend

The frontend consists of two main interfaces built with React:

Admin Panel: A comprehensive dashboard where users create boards, configure widgets, upload media, and manage content. The interface uses a drag-and-drop system for arranging widgets and provides real-time previews of how displays will appear. Form-based configuration makes it easy to set up data sources, customize appearance, and schedule content.

Display View: The public-facing board interface that actual display devices access. Optimized for full-screen presentation, it renders widgets in real-time based on the board configuration. The display view automatically reconnects if connectivity is lost and refreshes content according to configured intervals. Responsive design ensures proper rendering across different screen sizes and orientations.

Widget System

Digiboardi’s modular widget architecture is its most powerful feature. Each widget is a self-contained component that handles a specific type of content:

Clock Widget: Displays current time with multiple format options (12/24 hour, with/without seconds, analog/digital styles). Supports timezone configuration for international deployments.

Weather Widget: Shows current conditions and forecasts using data from external weather APIs. Configurable to display temperature, precipitation, wind, humidity, and multi-day forecasts. Supports multiple location configurations.

Calendar Widget: Integrates with calendar services to display upcoming events and appointments. Can sync with Google Calendar, iCal feeds, or custom calendar APIs. Shows event titles, times, and locations with configurable date ranges.

RSS Feed Widget: Displays news and updates from RSS/Atom feeds. Supports multiple feed sources with automatic rotation. Configurable display duration and article count.

Announcement Widget: Shows custom text messages and announcements. Supports rich text formatting, images, and scheduled display times. Useful for internal communications and temporary notices.

Image/Video Widget: Displays static images or video content. Supports rotation through multiple media files with configurable duration. Handles common formats (JPEG, PNG, GIF, MP4, WebM).

Transit Widget: Shows real-time public transit information including arrival times and service alerts. Integrates with transit APIs for buses, trains, and subways.

Social Media Widget: Displays social media feeds from Twitter, Instagram, or custom sources. Shows recent posts with automatic refresh.

Each widget includes configuration options for appearance (size, position, colors), data source, refresh interval, and display behavior.

Data Management

Digiboardi stores data in several layers:

Database: SQLite by default for simplicity, with PostgreSQL support for larger deployments. Stores:

  • Board configurations and metadata
  • Widget settings and associations
  • User accounts and authentication tokens
  • Media file references and metadata
  • Scheduled content and display rules

File Storage: Media files (images, videos) stored in the filesystem with references in the database. Supports local storage or cloud storage backends.

Cache Layer: In-memory caching of frequently accessed data like weather information and API responses to reduce external API calls and improve performance.

Real-Time Communication

WebSocket connections enable instant content updates:

  1. Admin makes a change in the control panel
  2. Backend validates and saves the change
  3. WebSocket server broadcasts update to all connected displays
  4. Display clients receive update and re-render affected widgets
  5. Changes appear on screens within milliseconds

This architecture eliminates the need for displays to constantly poll for updates, reducing bandwidth and improving responsiveness.

Display Client Architecture

The display view operates as a single-page application:

Initial Load:

  1. Display navigates to board-specific URL
  2. Fetches board configuration from API
  3. Loads required widget components
  4. Establishes WebSocket connection
  5. Renders initial content

Runtime Operation:

  • Widgets refresh independently based on configured intervals
  • WebSocket receives real-time updates from server
  • Automatic reconnection if connection drops
  • Handles network interruptions gracefully
  • Logs errors for troubleshooting

Rendering Pipeline:

  • React renders widget components in grid layout
  • CSS handles responsive positioning and sizing
  • Media files lazy-loaded as needed
  • Smooth transitions between content states
  • Hardware-accelerated animations where supported

Network Architecture

Digiboardi requires the following network components:

HTTP/HTTPS Traffic: Primary protocol for web interface and API communication. All admin panel access and display loading happens over HTTP(S).

WebSocket Connections: Real-time bidirectional communication on same port as HTTP server. Used for pushing updates to displays.

External API Access: Backend may need to access external services for data:

  • Weather APIs (OpenWeatherMap, Weather.com, etc.)
  • Transit APIs (local transit authorities)
  • Calendar services (Google Calendar, CalDAV)
  • Social media APIs (Twitter, Instagram)

Displays only need outbound HTTP/HTTPS access to the Digiboardi server, making them suitable for restricted networks.

Storage Requirements

Digiboardi’s storage needs depend on usage:

  • Application: 100-200MB for Node.js, dependencies, and frontend assets
  • Database: 10-100MB depending on number of boards and widgets
  • Media Files: Highly variable based on content (plan 1-10GB for typical usage)
  • Logs: 50-200MB with rotation enabled
  • Cache: 10-50MB for API response caching

Typical deployment allocations:

  • Small Deployment (1-5 displays): 5GB total
  • Medium Deployment (5-20 displays): 15GB total
  • Large Deployment (20+ displays): 30GB+ total

Installation and Setup

Let’s walk through setting up Digiboardi for deployment on Klutch.sh.

Step 1: Create the Project Structure

First, create a new directory for your Digiboardi deployment:

Terminal window
mkdir digiboardi-deployment
cd digiboardi-deployment
git init

Step 2: Create the Dockerfile

Create a Dockerfile in the root directory:

FROM node:18-alpine
# Set working directory
WORKDIR /app
# Install system dependencies
RUN apk add --no-cache \
git \
python3 \
make \
g++ \
sqlite \
sqlite-dev
# Set environment variables
ENV NODE_ENV=production \
PORT=3000 \
DATABASE_URL=sqlite:///data/digiboardi.db
# Clone Digiboardi repository
RUN git clone https://github.com/yourusername/digiboardi.git /app/src && \
cd /app/src && \
npm install --production
# Create necessary directories
RUN mkdir -p /data /app/uploads /app/logs
# Copy configuration files
COPY config.json /app/config.json
COPY entrypoint.sh /app/entrypoint.sh
RUN chmod +x /app/entrypoint.sh
# Build frontend
RUN cd /app/src/frontend && \
npm run build
# Expose port
EXPOSE 3000
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD node healthcheck.js || exit 1
# Start application
CMD ["/app/entrypoint.sh"]

Step 3: Create Configuration File

Create config.json:

{
"server": {
"port": 3000,
"host": "0.0.0.0",
"corsEnabled": true,
"corsOrigins": ["*"]
},
"database": {
"type": "sqlite",
"path": "/data/digiboardi.db",
"logging": false
},
"storage": {
"type": "local",
"uploadDir": "/app/uploads",
"maxFileSize": 52428800,
"allowedTypes": ["image/jpeg", "image/png", "image/gif", "video/mp4", "video/webm"]
},
"authentication": {
"enabled": true,
"sessionSecret": "change-this-secret-in-production",
"sessionMaxAge": 86400000,
"bcryptRounds": 10
},
"websocket": {
"enabled": true,
"pingInterval": 30000,
"pingTimeout": 60000
},
"widgets": {
"weather": {
"enabled": true,
"apiProvider": "openweathermap",
"apiKey": "",
"cacheTimeout": 600000
},
"transit": {
"enabled": true,
"cacheTimeout": 300000
},
"calendar": {
"enabled": true,
"syncInterval": 300000
},
"rss": {
"enabled": true,
"refreshInterval": 600000
}
},
"display": {
"defaultRefreshInterval": 60000,
"reconnectInterval": 5000,
"maxReconnectAttempts": 10
},
"logging": {
"level": "info",
"file": "/app/logs/digiboardi.log",
"maxSize": "10m",
"maxFiles": 5
}
}

Step 4: Create Entrypoint Script

Create entrypoint.sh:

#!/bin/sh
set -e
echo "Starting Digiboardi..."
# Wait for database to be ready
if [ ! -f /data/digiboardi.db ]; then
echo "Initializing database..."
cd /app/src
npm run migrate
fi
# Create default admin user if none exists
cd /app/src
node scripts/create-admin.js
# Start application
echo "Starting server on port ${PORT:-3000}..."
exec node server.js

Step 5: Create Admin User Script

Create scripts/create-admin.js:

const bcrypt = require('bcrypt');
const sqlite3 = require('sqlite3').verbose();
const DB_PATH = process.env.DATABASE_URL || '/data/digiboardi.db';
const ADMIN_USERNAME = process.env.ADMIN_USERNAME || 'admin';
const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || 'changeme';
const ADMIN_EMAIL = process.env.ADMIN_EMAIL || 'admin@example.com';
async function createAdmin() {
const db = new sqlite3.Database(DB_PATH.replace('sqlite://', ''));
return new Promise((resolve, reject) => {
// Check if admin exists
db.get('SELECT id FROM users WHERE username = ?', [ADMIN_USERNAME], async (err, row) => {
if (err) {
reject(err);
return;
}
if (row) {
console.log('Admin user already exists');
db.close();
resolve();
return;
}
// Create admin user
const hashedPassword = await bcrypt.hash(ADMIN_PASSWORD, 10);
db.run(
'INSERT INTO users (username, email, password, role, created_at) VALUES (?, ?, ?, ?, ?)',
[ADMIN_USERNAME, ADMIN_EMAIL, hashedPassword, 'admin', new Date().toISOString()],
(err) => {
if (err) {
reject(err);
return;
}
console.log(`Admin user created: ${ADMIN_USERNAME}`);
console.log(`Default password: ${ADMIN_PASSWORD}`);
console.log('Please change the password after first login!');
db.close();
resolve();
}
);
});
});
}
createAdmin().catch(console.error);

Step 6: Create Health Check Script

Create healthcheck.js:

const http = require('http');
const options = {
host: 'localhost',
port: process.env.PORT || 3000,
path: '/api/health',
timeout: 2000
};
const request = http.request(options, (res) => {
if (res.statusCode === 200) {
process.exit(0);
} else {
process.exit(1);
}
});
request.on('error', () => {
process.exit(1);
});
request.end();

Step 7: Create Environment Configuration

Create .env.example:

Terminal window
# Server Configuration
PORT=3000
NODE_ENV=production
HOST=0.0.0.0
# Database
DATABASE_URL=sqlite:///data/digiboardi.db
# Authentication
SESSION_SECRET=your-secure-random-secret-here
ADMIN_USERNAME=admin
ADMIN_PASSWORD=changeme
ADMIN_EMAIL=admin@example.com
# Storage
UPLOAD_DIR=/app/uploads
MAX_FILE_SIZE=52428800
# Weather Widget
WEATHER_API_PROVIDER=openweathermap
WEATHER_API_KEY=your-api-key-here
# Transit Widget (optional)
TRANSIT_API_KEY=your-transit-api-key
# Calendar Widget (optional)
GOOGLE_CALENDAR_API_KEY=your-google-api-key
GOOGLE_CALENDAR_ID=your-calendar-id
# Social Media (optional)
TWITTER_API_KEY=your-twitter-api-key
TWITTER_API_SECRET=your-twitter-api-secret
# Display Settings
DEFAULT_REFRESH_INTERVAL=60000
WEBSOCKET_PING_INTERVAL=30000
# Logging
LOG_LEVEL=info
LOG_FILE=/app/logs/digiboardi.log

Step 8: Create Sample Board Configuration

Create sample-board.json:

{
"name": "Office Lobby Display",
"description": "Main lobby information board",
"layout": "grid",
"theme": "light",
"refreshInterval": 60000,
"widgets": [
{
"type": "clock",
"position": { "x": 0, "y": 0, "width": 4, "height": 2 },
"config": {
"format": "12h",
"showSeconds": true,
"showDate": true,
"timezone": "America/New_York"
}
},
{
"type": "weather",
"position": { "x": 4, "y": 0, "width": 4, "height": 2 },
"config": {
"location": "New York, NY",
"units": "imperial",
"showForecast": true,
"forecastDays": 3
}
},
{
"type": "announcement",
"position": { "x": 0, "y": 2, "width": 8, "height": 2 },
"config": {
"title": "Welcome",
"message": "Welcome to our office! Check the board for today's updates.",
"backgroundColor": "#4a90e2",
"textColor": "#ffffff"
}
},
{
"type": "calendar",
"position": { "x": 0, "y": 4, "width": 4, "height": 4 },
"config": {
"title": "Today's Events",
"maxEvents": 5,
"showTime": true,
"showLocation": true
}
},
{
"type": "rss",
"position": { "x": 4, "y": 4, "width": 4, "height": 4 },
"config": {
"title": "News Feed",
"feeds": [
"https://feeds.bbci.co.uk/news/rss.xml",
"https://rss.nytimes.com/services/xml/rss/nyt/HomePage.xml"
],
"maxItems": 5,
"itemDuration": 10000
}
}
]
}

Step 9: Create Widget Templates

Create widget-templates/weather-widget.jsx:

import React, { useState, useEffect } from 'react';
import './weather-widget.css';
const WeatherWidget = ({ config }) => {
const [weather, setWeather] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchWeather = async () => {
try {
const response = await fetch(`/api/widgets/weather?location=${config.location}&units=${config.units}`);
const data = await response.json();
setWeather(data);
setLoading(false);
} catch (error) {
console.error('Failed to fetch weather:', error);
setLoading(false);
}
};
fetchWeather();
const interval = setInterval(fetchWeather, config.refreshInterval || 600000);
return () => clearInterval(interval);
}, [config]);
if (loading) {
return <div className="widget weather-widget loading">Loading weather...</div>;
}
if (!weather) {
return <div className="widget weather-widget error">Unable to load weather</div>;
}
return (
<div className="widget weather-widget">
<div className="weather-current">
<div className="weather-icon">
<img src={`/assets/weather/${weather.icon}.svg`} alt={weather.description} />
</div>
<div className="weather-temp">
{Math.round(weather.temp)}°{config.units === 'imperial' ? 'F' : 'C'}
</div>
<div className="weather-description">{weather.description}</div>
<div className="weather-location">{config.location}</div>
</div>
{config.showForecast && weather.forecast && (
<div className="weather-forecast">
{weather.forecast.slice(0, config.forecastDays || 3).map((day, index) => (
<div key={index} className="forecast-day">
<div className="forecast-date">{day.date}</div>
<img src={`/assets/weather/${day.icon}.svg`} alt={day.description} />
<div className="forecast-temp">
{Math.round(day.high)}° / {Math.round(day.low)}°
</div>
</div>
))}
</div>
)}
</div>
);
};
export default WeatherWidget;

Step 10: Create Docker Ignore File

Create .dockerignore:

.git
.gitignore
*.md
README.md
.env
.env.local
.env.production
.DS_Store
Thumbs.db
node_modules/
npm-debug.log
yarn-error.log
*.log
.vscode
.idea
dist/
build/
coverage/
.cache/

Step 11: Create Documentation

Create README.md:

# Digiboardi Deployment
This repository contains a Digiboardi deployment configured for Klutch.sh.
## Features
- Modern digital signage platform
- Real-time content updates via WebSocket
- Modular widget system
- Multiple board support
- Responsive design for any screen size
- Media file management
- User authentication
- Calendar and RSS feed integration
- Weather and transit widgets
## Default Credentials
- Username: `admin`
- Password: `changeme`
**IMPORTANT: Change the default password immediately after first login!**
## Quick Start
1. Deploy to Klutch.sh
2. Access the admin panel at your deployment URL
3. Login with default credentials
4. Change admin password in settings
5. Create your first board
6. Add widgets to the board
7. Access display view from any device
## Creating a Board
1. Click "Create New Board" in admin panel
2. Enter board name and description
3. Choose layout (grid, flex, custom)
4. Add widgets by dragging from widget library
5. Configure each widget's settings
6. Save board
## Widget Types
- **Clock**: Display current time
- **Weather**: Show weather forecasts
- **Calendar**: Display upcoming events
- **Announcement**: Custom messages
- **RSS**: News feeds
- **Image/Video**: Media content
- **Transit**: Public transit times
- **Social Media**: Social feeds
## Display Setup
Access board display:

https://your-app.klutch.sh/display/{board-id}

For full-screen kiosk mode:

https://your-app.klutch.sh/display/{board-id}?fullscreen=true

## Configuration
Edit environment variables:
- Database settings
- API keys for external services
- Upload limits
- Session configuration
- Widget refresh intervals
## API Endpoints
- `GET /api/boards` - List all boards
- `POST /api/boards` - Create new board
- `GET /api/boards/:id` - Get board details
- `PUT /api/boards/:id` - Update board
- `DELETE /api/boards/:id` - Delete board
- `GET /api/widgets` - Available widget types
- `POST /api/upload` - Upload media file
## Deployment
This application is configured to deploy on Klutch.sh with automatic Docker detection.

Step 12: Initialize Git Repository

Terminal window
git add .
git commit -m "Initial Digiboardi setup for Klutch.sh deployment"
git branch -M master
git remote add origin https://github.com/yourusername/digiboardi-deployment.git
git push -u origin master

Deploying to Klutch.sh

Now that your Digiboardi application is configured, let’s deploy it to Klutch.sh.

  1. Log in to Klutch.sh

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

  2. Create a New Project

    Click “New Project” and select “Import from GitHub”. Choose the repository containing your Digiboardi deployment.

  3. Configure Build Settings

    Klutch.sh will automatically detect the Dockerfile in your repository. The platform will use this for building your container.

  4. Configure Traffic Settings

    Select “HTTP” as the traffic type. Digiboardi’s web interface runs on port 3000, and Klutch.sh will route HTTPS traffic to this port.

  5. Set Environment Variables

    In the project settings, add the following environment variables:

    • PORT: 3000
    • NODE_ENV: production
    • SESSION_SECRET: Generate a secure random string
    • ADMIN_USERNAME: admin (or your preferred username)
    • ADMIN_PASSWORD: Set a secure password
    • ADMIN_EMAIL: Your email address

    Optional widget API keys:

    • WEATHER_API_KEY: Your OpenWeatherMap API key
    • GOOGLE_CALENDAR_API_KEY: For calendar widget
    • TRANSIT_API_KEY: For transit widget
    • TWITTER_API_KEY: For social media widget

    Display configuration:

    • DEFAULT_REFRESH_INTERVAL: 60000 (60 seconds)
    • WEBSOCKET_PING_INTERVAL: 30000 (30 seconds)
  6. Configure Persistent Storage

    Digiboardi requires persistent storage for database and media files:

    • Database Volume:
      • Mount path: /data
      • Size: 5GB
    • Uploads Volume:
      • Mount path: /app/uploads
      • Size: 10GB (adjust based on media needs)
    • Logs Volume (optional):
      • Mount path: /app/logs
      • Size: 2GB

    These volumes ensure your boards, configurations, and media files persist across deployments.

  7. Deploy the Application

    Click “Deploy” to start the build process. Klutch.sh will:

    • Clone your repository
    • Build the Docker image using your Dockerfile
    • Install Node.js dependencies
    • Build the React frontend
    • Initialize the database
    • Create the admin user
    • Deploy the container
    • Provision an HTTPS endpoint

    The build process typically takes 4-6 minutes.

  8. Access the Admin Panel

    Once deployment completes, access your Digiboardi admin panel at the provided URL (e.g., example-app.klutch.sh).

    Login with your configured credentials and immediately change the default password if you used one.

  9. Create Your First Board

    After logging in:

    • Click "Create New Board"
    • Enter a name and description
    • Select a layout template or start from scratch
    • Click "Create" to initialize the board

Getting Started with Digiboardi

Once your Digiboardi instance is deployed, here’s how to create and manage digital displays:

Creating Your First Board

Step 1: Access Admin Panel

Navigate to your deployment URL and login with your admin credentials.

Step 2: Create Board

// Board creation via API (optional)
const createBoard = async () => {
const response = await fetch('https://example-app.klutch.sh/api/boards', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authToken}`
},
body: JSON.stringify({
name: 'Office Lobby Display',
description: 'Main entrance information board',
layout: 'grid',
theme: 'light',
orientation: 'landscape'
})
});
const board = await response.json();
console.log('Board created:', board.id);
return board;
};

Or use the web interface:

  1. Click “Boards” in navigation
  2. Click “New Board” button
  3. Fill in board details:
    • Name: Descriptive name for the board
    • Description: Optional description
    • Layout: Grid (structured) or Flex (free-form)
    • Theme: Light or Dark
    • Orientation: Landscape or Portrait
  4. Click “Create Board”

Adding Widgets

Clock Widget

Add a clock to show current time:

  1. Drag “Clock” widget from widget library
  2. Position on board grid
  3. Configure settings:
    • Format: 12-hour or 24-hour
    • Show seconds: Yes/No
    • Show date: Yes/No
    • Timezone: Select timezone
    • Font size: Small/Medium/Large

Configuration example:

{
"type": "clock",
"config": {
"format": "12h",
"showSeconds": true,
"showDate": true,
"timezone": "America/New_York",
"fontSize": "large"
}
}

Weather Widget

Display weather information:

  1. Drag “Weather” widget to board
  2. Configure settings:
    • Location: City name or coordinates
    • Units: Imperial (°F) or Metric (°C)
    • Show forecast: Enable/disable
    • Forecast days: 1-7 days
    • Refresh interval: Minutes between updates

Configuration example:

{
"type": "weather",
"config": {
"location": "San Francisco, CA",
"units": "imperial",
"showForecast": true,
"forecastDays": 5,
"refreshInterval": 600000
}
}

Announcement Widget

Create custom announcements:

  1. Add “Announcement” widget
  2. Enter content:
    • Title: Announcement headline
    • Message: Main text (supports HTML)
    • Background color: Hex color code
    • Text color: Hex color code
    • Duration: Display time (0 = always show)

Example announcement:

{
"type": "announcement",
"config": {
"title": "Important Notice",
"message": "<p>Team meeting at 3 PM in Conference Room A</p>",
"backgroundColor": "#e74c3c",
"textColor": "#ffffff",
"fontSize": "medium",
"duration": 0
}
}

Calendar Widget

Display upcoming events:

  1. Add “Calendar” widget
  2. Connect calendar source:
    • Google Calendar: Enter Calendar ID and API key
    • iCal feed: Provide feed URL
    • Manual events: Add events directly
  3. Configure display:
    • Max events: Number of events to show
    • Date range: Today, This week, This month
    • Show time: Display event times
    • Show location: Display event locations

Configuration:

{
"type": "calendar",
"config": {
"source": "google",
"calendarId": "your-calendar-id@group.calendar.google.com",
"apiKey": "your-google-api-key",
"maxEvents": 5,
"dateRange": "week",
"showTime": true,
"showLocation": true,
"refreshInterval": 300000
}
}

RSS Feed Widget

Display news and updates:

  1. Add “RSS Feed” widget
  2. Add feed URLs (supports multiple feeds)
  3. Configure display:
    • Max items: Number of articles to show
    • Item duration: Seconds per article
    • Show images: Display article images
    • Rotation: Automatic or manual

Configuration:

{
"type": "rss",
"config": {
"feeds": [
"https://feeds.bbci.co.uk/news/rss.xml",
"https://rss.nytimes.com/services/xml/rss/nyt/Technology.xml"
],
"maxItems": 10,
"itemDuration": 10000,
"showImages": true,
"refreshInterval": 600000
}
}

Image/Video Widget

Display media content:

  1. Add “Media” widget
  2. Upload files or provide URLs
  3. Configure playback:
    • Files: Select uploaded media
    • Duration: Seconds per item (images only)
    • Loop video: Enable/disable
    • Autoplay: Start automatically
    • Rotation: Cycle through multiple files

Upload media via API:

const uploadMedia = async (file) => {
const formData = new FormData();
formData.append('file', file);
formData.append('boardId', boardId);
const response = await fetch('https://example-app.klutch.sh/api/upload', {
method: 'POST',
headers: {
'Authorization': `Bearer ${authToken}`
},
body: formData
});
const data = await response.json();
return data.fileUrl;
};

Configuring Board Layout

Grid Layout

Grid layout divides the screen into a 12-column grid:

// Widget positioning in grid
{
"position": {
"x": 0, // Column start (0-11)
"y": 0, // Row start
"width": 4, // Columns wide
"height": 2 // Rows tall
}
}

Example layout:

  • Clock: x=0, y=0, width=4, height=2 (top-left, 4 columns wide)
  • Weather: x=4, y=0, width=4, height=2 (top-center)
  • Announcement: x=8, y=0, width=4, height=2 (top-right)
  • Calendar: x=0, y=2, width=6, height=4 (bottom-left, half width)
  • RSS: x=6, y=2, width=6, height=4 (bottom-right, half width)

Flex Layout

Flex layout allows free positioning:

{
"position": {
"top": "10%",
"left": "5%",
"width": "40%",
"height": "30%"
}
}

Display Management

Connecting Displays

Each board has a unique display URL:

https://example-app.klutch.sh/display/{board-id}

Configure display devices:

Browser-Based Displays (Raspberry Pi, tablets, computers):

  1. Open browser on display device
  2. Navigate to display URL
  3. Press F11 for full-screen mode
  4. Bookmark for easy access

Kiosk Mode

For dedicated displays, use kiosk mode:

https://example-app.klutch.sh/display/{board-id}?fullscreen=true&hideCursor=true

Parameters:

  • fullscreen=true: Auto full-screen
  • hideCursor=true: Hide mouse cursor
  • rotation=90: Rotate display (0, 90, 180, 270)
  • refresh=60: Force refresh interval (seconds)

Auto-Start on Boot (Raspberry Pi example):

# Create startup script
cat << 'EOF' > /home/pi/start-display.sh
#!/bin/bash
sleep 10
chromium-browser \
--kiosk \
--noerrdialogs \
--disable-infobars \
--no-first-run \
--disable-session-crashed-bubble \
--disable-translate \
--disable-features=TranslateUI \
"https://example-app.klutch.sh/display/abc123?fullscreen=true"
EOF
chmod +x /home/pi/start-display.sh
# Add to autostart
mkdir -p /home/pi/.config/autostart
cat << 'EOF' > /home/pi/.config/autostart/digiboardi.desktop
[Desktop Entry]
Type=Application
Name=Digiboardi Display
Exec=/home/pi/start-display.sh
EOF

Real-Time Updates

Updates happen automatically via WebSocket:

Making Changes:

  1. Edit board in admin panel
  2. Modify widget settings
  3. Add/remove widgets
  4. Upload new media
  5. Changes appear on displays within seconds

Manual Refresh: Displays can be refreshed manually by adding ?t={timestamp} to URL or pressing Ctrl+R on display device.

Connection Status: Display shows connection indicator:

  • Green: Connected and receiving updates
  • Yellow: Reconnecting
  • Red: Disconnected (will auto-reconnect)

API Integration

Get Board Configuration

const getBoard = async (boardId) => {
const response = await fetch(`https://example-app.klutch.sh/api/boards/${boardId}`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
const board = await response.json();
return board;
};

Update Widget

const updateWidget = async (boardId, widgetId, config) => {
const response = await fetch(
`https://example-app.klutch.sh/api/boards/${boardId}/widgets/${widgetId}`,
{
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authToken}`
},
body: JSON.stringify({ config })
}
);
return response.json();
};

Delete Widget

const deleteWidget = async (boardId, widgetId) => {
await fetch(
`https://example-app.klutch.sh/api/boards/${boardId}/widgets/${widgetId}`,
{
method: 'DELETE',
headers: {
'Authorization': `Bearer ${authToken}`
}
}
);
};

Advanced Configuration

Custom Widget Development

Create custom widgets for specialized content:

Widget Structure

custom-widget.jsx
import React, { useState, useEffect } from 'react';
import './custom-widget.css';
const CustomWidget = ({ config, boardId, widgetId }) => {
const [data, setData] = useState(null);
useEffect(() => {
// Fetch data
const fetchData = async () => {
try {
const response = await fetch(config.dataSource);
const result = await response.json();
setData(result);
} catch (error) {
console.error('Error fetching data:', error);
}
};
fetchData();
// Set up refresh interval
const interval = setInterval(fetchData, config.refreshInterval || 60000);
return () => clearInterval(interval);
}, [config]);
return (
<div className="widget custom-widget">
<h3>{config.title}</h3>
<div className="widget-content">
{data ? (
// Render your custom content
<pre>{JSON.stringify(data, null, 2)}</pre>
) : (
<p>Loading...</p>
)}
</div>
</div>
);
};
export default CustomWidget;

Register Custom Widget

widgets/index.js
import CustomWidget from './custom-widget';
export const widgetRegistry = {
clock: ClockWidget,
weather: WeatherWidget,
calendar: CalendarWidget,
announcement: AnnouncementWidget,
rss: RSSWidget,
media: MediaWidget,
custom: CustomWidget // Register custom widget
};

Multi-Display Management

Manage multiple displays from one instance:

Create Display Groups

const createDisplayGroup = async (name, boardIds) => {
const response = await fetch('https://example-app.klutch.sh/api/groups', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authToken}`
},
body: JSON.stringify({
name,
boards: boardIds,
syncEnabled: true
})
});
return response.json();
};

Schedule Board Rotation

Display different boards at different times:

{
"displayId": "display-001",
"schedule": [
{
"boardId": "morning-board",
"days": [1, 2, 3, 4, 5],
"startTime": "06:00",
"endTime": "12:00"
},
{
"boardId": "afternoon-board",
"days": [1, 2, 3, 4, 5],
"startTime": "12:00",
"endTime": "18:00"
},
{
"boardId": "evening-board",
"days": [1, 2, 3, 4, 5],
"startTime": "18:00",
"endTime": "22:00"
}
]
}

Database Migration to PostgreSQL

For production deployments with many displays, migrate to PostgreSQL:

Update Configuration

{
"database": {
"type": "postgresql",
"host": "postgres-host",
"port": 5432,
"database": "digiboardi",
"username": "digiboardi_user",
"password": "secure_password",
"ssl": true,
"logging": false
}
}

Environment Variables

Terminal window
DATABASE_URL=postgresql://user:password@host:5432/digiboardi

Content Scheduling

Schedule content display times:

const scheduleContent = async (widgetId, schedule) => {
const response = await fetch(
`https://example-app.klutch.sh/api/widgets/${widgetId}/schedule`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authToken}`
},
body: JSON.stringify({
enabled: true,
timezone: 'America/New_York',
rules: [
{
days: ['monday', 'tuesday', 'wednesday', 'thursday', 'friday'],
startTime: '08:00',
endTime: '17:00',
visible: true
},
{
days: ['saturday', 'sunday'],
startTime: '00:00',
endTime: '23:59',
visible: false
}
]
})
}
);
return response.json();
};

Performance Optimization

Image Optimization

Optimize images before upload:

const optimizeImage = async (file) => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const img = new Image();
return new Promise((resolve) => {
img.onload = () => {
// Set max dimensions
const maxWidth = 1920;
const maxHeight = 1080;
let width = img.width;
let height = img.height;
if (width > maxWidth || height > maxHeight) {
const ratio = Math.min(maxWidth / width, maxHeight / height);
width *= ratio;
height *= ratio;
}
canvas.width = width;
canvas.height = height;
ctx.drawImage(img, 0, 0, width, height);
canvas.toBlob((blob) => {
resolve(new File([blob], file.name, { type: 'image/jpeg' }));
}, 'image/jpeg', 0.85);
};
img.src = URL.createObjectURL(file);
});
};

Caching Strategy

Implement caching for external API calls:

const cache = new Map();
const getCachedData = async (key, fetchFn, ttl = 600000) => {
const cached = cache.get(key);
if (cached && Date.now() - cached.timestamp < ttl) {
return cached.data;
}
const data = await fetchFn();
cache.set(key, { data, timestamp: Date.now() });
return data;
};
// Usage
const weather = await getCachedData(
`weather-${location}`,
() => fetchWeatherData(location),
600000 // 10 minutes
);

Production Best Practices

Follow these recommendations for running Digiboardi in production:

Security

Change Default Credentials

Immediately change admin password:

const changePassword = async (currentPassword, newPassword) => {
const response = await fetch('https://example-app.klutch.sh/api/auth/change-password', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authToken}`
},
body: JSON.stringify({
currentPassword,
newPassword
})
});
return response.json();
};

Secure Session Configuration

Use strong session secrets:

Terminal window
# Generate secure secret
openssl rand -base64 32
# Set in environment
SESSION_SECRET=your-generated-secret-here

API Key Management

Store API keys securely:

  • Never commit API keys to version control
  • Use environment variables
  • Rotate keys regularly
  • Restrict API key permissions to minimum required

Content Security Policy

Add CSP headers in Nginx/Apache:

add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' wss:;";

Reliability

Monitor Display Connectivity

Track display connections:

const monitorDisplays = () => {
const displays = new Map();
io.on('connection', (socket) => {
displays.set(socket.id, {
boardId: socket.handshake.query.boardId,
connectedAt: Date.now(),
lastPing: Date.now()
});
socket.on('ping', () => {
const display = displays.get(socket.id);
if (display) {
display.lastPing = Date.now();
}
});
socket.on('disconnect', () => {
displays.delete(socket.id);
});
});
// Check for stale connections
setInterval(() => {
const now = Date.now();
displays.forEach((display, socketId) => {
if (now - display.lastPing > 60000) {
console.warn(`Display ${socketId} appears disconnected`);
}
});
}, 30000);
};

Automatic Failover

Configure displays to fallback:

// Display client code
const connectWithFailover = (primaryUrl, fallbackUrl) => {
let ws;
let useFallback = false;
const connect = (url) => {
ws = new WebSocket(url);
ws.onclose = () => {
if (!useFallback && fallbackUrl) {
console.log('Primary connection failed, trying fallback');
useFallback = true;
setTimeout(() => connect(fallbackUrl), 1000);
} else {
setTimeout(() => connect(primaryUrl), 5000);
}
};
};
connect(primaryUrl);
};

Health Monitoring

Implement comprehensive health checks:

app.get('/api/health', async (req, res) => {
const health = {
status: 'healthy',
timestamp: new Date().toISOString(),
checks: {}
};
// Database check
try {
await db.query('SELECT 1');
health.checks.database = 'ok';
} catch (error) {
health.checks.database = 'error';
health.status = 'unhealthy';
}
// WebSocket check
health.checks.websocket = io.engine.clientsCount > 0 ? 'ok' : 'no_clients';
// Storage check
try {
const stats = await fs.statfs('/data');
const usagePercent = (1 - stats.bavail / stats.blocks) * 100;
health.checks.storage = {
status: usagePercent < 90 ? 'ok' : 'warning',
usagePercent: usagePercent.toFixed(2)
};
} catch (error) {
health.checks.storage = 'error';
}
res.status(health.status === 'healthy' ? 200 : 503).json(health);
});

Performance

Optimize WebSocket Connections

Configure WebSocket for efficiency:

const io = socketIO(server, {
pingInterval: 25000,
pingTimeout: 60000,
transports: ['websocket', 'polling'],
allowUpgrades: true,
perMessageDeflate: {
threshold: 1024
}
});

Lazy Load Media

Load images and videos on-demand:

const LazyMedia = ({ src, type, alt }) => {
const [isLoaded, setIsLoaded] = useState(false);
const imgRef = useRef();
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsLoaded(true);
observer.disconnect();
}
},
{ rootMargin: '50px' }
);
if (imgRef.current) {
observer.observe(imgRef.current);
}
return () => observer.disconnect();
}, []);
return (
<div ref={imgRef}>
{isLoaded ? (
type === 'video' ? (
<video src={src} autoPlay loop muted />
) : (
<img src={src} alt={alt} />
)
) : (
<div className="placeholder">Loading...</div>
)}
</div>
);
};

Database Optimization

Add indexes for common queries:

-- Index on boards
CREATE INDEX idx_boards_user ON boards(user_id);
CREATE INDEX idx_boards_created ON boards(created_at DESC);
-- Index on widgets
CREATE INDEX idx_widgets_board ON widgets(board_id);
CREATE INDEX idx_widgets_type ON widgets(type);
-- Index on media
CREATE INDEX idx_media_board ON media(board_id);
CREATE INDEX idx_media_created ON media(created_at DESC);

Resource Limits

Configure memory and CPU limits:

{
"upload": {
"maxFileSize": 52428800,
"maxFiles": 10,
"allowedTypes": ["image/jpeg", "image/png", "image/gif", "video/mp4"]
},
"api": {
"rateLimitWindow": 900000,
"rateLimitMax": 100
},
"websocket": {
"maxConnections": 1000,
"messageRateLimit": 10
}
}

Troubleshooting

Common Issues

Problem: Displays not connecting to server

Solutions:

  • Check WebSocket URL is correct
  • Verify firewall allows WebSocket connections
  • Check browser console for errors
  • Ensure display URL includes board ID
  • Verify server WebSocket is running: Check logs for WebSocket server startup

Problem: Widgets not updating in real-time

Solutions:

  • Check WebSocket connection status in browser console
  • Verify pingInterval and pingTimeout settings
  • Check server logs for WebSocket errors
  • Restart display browser to re-establish connection
  • Check for network interruptions

Problem: Images or videos not displaying

Solutions:

  • Verify files uploaded successfully
  • Check file size within limits (default 50MB)
  • Ensure file format supported (JPEG, PNG, GIF, MP4, WebM)
  • Check browser console for 404 errors
  • Verify storage volume mounted correctly
  • Check file permissions in uploads directory

Display Issues

Problem: Display shows blank screen

Solutions:

  • Verify board ID in URL is correct
  • Check board exists and has widgets
  • Open browser console for errors
  • Test display URL in different browser
  • Verify JavaScript enabled
  • Check for CSS loading issues

Problem: Layout broken or widgets overlapping

Solutions:

  • Check widget positions don’t conflict
  • Verify screen resolution matches expected size
  • Test with different zoom levels
  • Review grid configuration
  • Check browser compatibility
  • Clear browser cache

Problem: Display refreshes constantly

Solutions:

  • Check refresh interval not too short
  • Verify no infinite loop in custom widgets
  • Check for memory leaks in browser
  • Review WebSocket reconnect logic
  • Check for conflicting JavaScript

Performance Issues

Problem: Slow admin panel response

Solutions:

  • Check database query performance
  • Review server logs for slow requests
  • Optimize database indexes
  • Check CPU/memory usage on server
  • Implement query result caching
  • Reduce number of widgets per board

Problem: High bandwidth usage

Solutions:

  • Optimize image file sizes
  • Enable compression on web server
  • Implement image lazy loading
  • Reduce refresh intervals
  • Use appropriate video codecs
  • Cache external API responses

Problem: WebSocket disconnections

Solutions:

  • Increase pingTimeout value
  • Check for network stability
  • Review firewall rules
  • Implement exponential backoff for reconnection
  • Check server resource limits
  • Monitor server load

Configuration Issues

Problem: API keys not working

Solutions:

  • Verify API keys copied correctly (no spaces)
  • Check API key permissions and quotas
  • Verify API service is active
  • Test API directly outside Digiboardi
  • Check environment variables set correctly
  • Review API provider documentation

Problem: Database errors on startup

Solutions:

  • Verify database file exists and has permissions
  • Check disk space available
  • Review migration logs
  • Try reinitializing database
  • Check SQLite version compatibility
  • Verify volume mounted correctly

Additional Resources

Conclusion

Digiboardi transforms how you communicate information through digital displays. Whether you’re running a single screen in a small office or managing dozens of displays across multiple locations, Digiboardi provides the tools you need without the complexity and cost of enterprise digital signage solutions. The modular widget system means you can display exactly the content you need—weather, calendars, announcements, news feeds, or custom data—all updating in real-time as you make changes.

Deploying Digiboardi on Klutch.sh gives you a reliable, always-on digital signage platform without the hassle of server maintenance. Your display server runs 24/7 with automatic HTTPS, persistent storage for your content and configuration, and the ability to manage everything remotely from anywhere. Displays connect directly to your Klutch.sh deployment, updating in real-time as you create and modify boards. Whether you’re setting up information boards for an office, creating menu displays for a restaurant, or building custom dashboards for data visualization, Digiboardi on Klutch.sh provides the foundation you need.

Start building your digital signage solution with Digiboardi today and bring dynamic, engaging content to any screen.