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:
- Admin makes a change in the control panel
- Backend validates and saves the change
- WebSocket server broadcasts update to all connected displays
- Display clients receive update and re-render affected widgets
- 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:
- Display navigates to board-specific URL
- Fetches board configuration from API
- Loads required widget components
- Establishes WebSocket connection
- 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:
mkdir digiboardi-deploymentcd digiboardi-deploymentgit initStep 2: Create the Dockerfile
Create a Dockerfile in the root directory:
FROM node:18-alpine
# Set working directoryWORKDIR /app
# Install system dependenciesRUN apk add --no-cache \ git \ python3 \ make \ g++ \ sqlite \ sqlite-dev
# Set environment variablesENV NODE_ENV=production \ PORT=3000 \ DATABASE_URL=sqlite:///data/digiboardi.db
# Clone Digiboardi repositoryRUN git clone https://github.com/yourusername/digiboardi.git /app/src && \ cd /app/src && \ npm install --production
# Create necessary directoriesRUN mkdir -p /data /app/uploads /app/logs
# Copy configuration filesCOPY config.json /app/config.jsonCOPY entrypoint.sh /app/entrypoint.sh
RUN chmod +x /app/entrypoint.sh
# Build frontendRUN cd /app/src/frontend && \ npm run build
# Expose portEXPOSE 3000
# Health checkHEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ CMD node healthcheck.js || exit 1
# Start applicationCMD ["/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/shset -e
echo "Starting Digiboardi..."
# Wait for database to be readyif [ ! -f /data/digiboardi.db ]; then echo "Initializing database..." cd /app/src npm run migratefi
# Create default admin user if none existscd /app/srcnode scripts/create-admin.js
# Start applicationecho "Starting server on port ${PORT:-3000}..."exec node server.jsStep 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:
# Server ConfigurationPORT=3000NODE_ENV=productionHOST=0.0.0.0
# DatabaseDATABASE_URL=sqlite:///data/digiboardi.db
# AuthenticationSESSION_SECRET=your-secure-random-secret-hereADMIN_USERNAME=adminADMIN_PASSWORD=changemeADMIN_EMAIL=admin@example.com
# StorageUPLOAD_DIR=/app/uploadsMAX_FILE_SIZE=52428800
# Weather WidgetWEATHER_API_PROVIDER=openweathermapWEATHER_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-keyGOOGLE_CALENDAR_ID=your-calendar-id
# Social Media (optional)TWITTER_API_KEY=your-twitter-api-keyTWITTER_API_SECRET=your-twitter-api-secret
# Display SettingsDEFAULT_REFRESH_INTERVAL=60000WEBSOCKET_PING_INTERVAL=30000
# LoggingLOG_LEVEL=infoLOG_FILE=/app/logs/digiboardi.logStep 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*.mdREADME.md.env.env.local.env.production.DS_StoreThumbs.dbnode_modules/npm-debug.logyarn-error.log*.log.vscode.ideadist/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.sh2. Access the admin panel at your deployment URL3. Login with default credentials4. Change admin password in settings5. Create your first board6. Add widgets to the board7. Access display view from any device
## Creating a Board
1. Click "Create New Board" in admin panel2. Enter board name and description3. Choose layout (grid, flex, custom)4. Add widgets by dragging from widget library5. Configure each widget's settings6. 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
git add .git commit -m "Initial Digiboardi setup for Klutch.sh deployment"git branch -M mastergit remote add origin https://github.com/yourusername/digiboardi-deployment.gitgit push -u origin masterDeploying to Klutch.sh
Now that your Digiboardi application is configured, let’s deploy it to Klutch.sh.
-
Log in to Klutch.sh
Navigate to klutch.sh/app and sign in with your GitHub account.
-
Create a New Project
Click “New Project” and select “Import from GitHub”. Choose the repository containing your Digiboardi deployment.
-
Configure Build Settings
Klutch.sh will automatically detect the Dockerfile in your repository. The platform will use this for building your container.
-
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.
-
Set Environment Variables
In the project settings, add the following environment variables:
PORT:3000NODE_ENV:productionSESSION_SECRET: Generate a secure random stringADMIN_USERNAME:admin(or your preferred username)ADMIN_PASSWORD: Set a secure passwordADMIN_EMAIL: Your email address
Optional widget API keys:
WEATHER_API_KEY: Your OpenWeatherMap API keyGOOGLE_CALENDAR_API_KEY: For calendar widgetTRANSIT_API_KEY: For transit widgetTWITTER_API_KEY: For social media widget
Display configuration:
DEFAULT_REFRESH_INTERVAL:60000(60 seconds)WEBSOCKET_PING_INTERVAL:30000(30 seconds)
-
Configure Persistent Storage
Digiboardi requires persistent storage for database and media files:
- Database Volume:
- Mount path:
/data - Size:
5GB
- Mount path:
- Uploads Volume:
- Mount path:
/app/uploads - Size:
10GB(adjust based on media needs)
- Mount path:
- Logs Volume (optional):
- Mount path:
/app/logs - Size:
2GB
- Mount path:
These volumes ensure your boards, configurations, and media files persist across deployments.
- Database Volume:
-
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.
-
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.
-
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:
- Click “Boards” in navigation
- Click “New Board” button
- 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
- Click “Create Board”
Adding Widgets
Clock Widget
Add a clock to show current time:
- Drag “Clock” widget from widget library
- Position on board grid
- 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:
- Drag “Weather” widget to board
- 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:
- Add “Announcement” widget
- 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:
- Add “Calendar” widget
- Connect calendar source:
- Google Calendar: Enter Calendar ID and API key
- iCal feed: Provide feed URL
- Manual events: Add events directly
- 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:
- Add “RSS Feed” widget
- Add feed URLs (supports multiple feeds)
- 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:
- Add “Media” widget
- Upload files or provide URLs
- 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):
- Open browser on display device
- Navigate to display URL
- Press F11 for full-screen mode
- Bookmark for easy access
Kiosk Mode
For dedicated displays, use kiosk mode:
https://example-app.klutch.sh/display/{board-id}?fullscreen=true&hideCursor=trueParameters:
fullscreen=true: Auto full-screenhideCursor=true: Hide mouse cursorrotation=90: Rotate display (0, 90, 180, 270)refresh=60: Force refresh interval (seconds)
Auto-Start on Boot (Raspberry Pi example):
# Create startup scriptcat << 'EOF' > /home/pi/start-display.sh#!/bin/bashsleep 10chromium-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 autostartmkdir -p /home/pi/.config/autostartcat << 'EOF' > /home/pi/.config/autostart/digiboardi.desktop[Desktop Entry]Type=ApplicationName=Digiboardi DisplayExec=/home/pi/start-display.shEOFReal-Time Updates
Updates happen automatically via WebSocket:
Making Changes:
- Edit board in admin panel
- Modify widget settings
- Add/remove widgets
- Upload new media
- 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
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
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
DATABASE_URL=postgresql://user:password@host:5432/digiboardiContent 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;};
// Usageconst 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:
# Generate secure secretopenssl rand -base64 32
# Set in environmentSESSION_SECRET=your-generated-secret-hereAPI 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 codeconst 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 boardsCREATE INDEX idx_boards_user ON boards(user_id);CREATE INDEX idx_boards_created ON boards(created_at DESC);
-- Index on widgetsCREATE INDEX idx_widgets_board ON widgets(board_id);CREATE INDEX idx_widgets_type ON widgets(type);
-- Index on mediaCREATE 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
- Digiboardi GitHub Repository
- Digiboardi Documentation
- OpenWeatherMap API
- Google Calendar API
- Socket.IO Documentation
- React Documentation
- Klutch.sh Documentation
- Persistent Volumes Guide
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.