A lightweight digital signage content provider, built as a full-stack web application using Next.js, Mantine, Reveal.js, Konva, and SQLite (Prisma).
- Display endpoints for mounted slideshows
- Live refresh for viewer devices via SSE with polling fallback
- WYSIWYG editor with drag/drop, resize, layers, ...
- Slides with text, images, videos, shapes, and more
- Animations and transitions via Reveal.js
- Use Google Fonts in text elements (with API key configured in Settings)
- Use Google Material Icons in icon elements
- Media Library with image + video assets
- Locked slide resolution (default 1920 x 540)
- Single-screen playback per slideshow
- Display management + slideshow mounting workflow
- Template system with default starter template
- SQLite persistence via Prisma
- Install dependencies
npm install- Configure the database (SQLite by default)
create a .env file in the root with the following content:
DATABASE_URL="file:./prisma/dev.db"
Optional startup override:
GOOGLE_FONTS_API_KEY="your-google-fonts-api-key"
When set at startup, this key is synced to tenant settings and the Google Fonts key field is hidden in the editor settings for that running process.
Optional (initial editor password):
DEFAULT_PASSWORD="your-initial-editor-password"
- Run Prisma migrations
npm run prisma:migrate- Start the dev server
npm run devImportant: SSE doesn't work in development mode, live refresh of the viewer falls back to long polling.
/editrequires a password login.- Default password is read from
DEFAULT_PASSWORD. - If
DEFAULT_PASSWORDis not set, fallback ispassword. - Use Settings -> Editor Authentication inside the editor to change it immediately.
- Use Settings -> Google Fonts inside the editor to configure or change the Google Fonts API key.
- Enable Remember me on login to keep access for 30 days via a secure HTTP-only cookie.
- Set
EDITOR_AUTH_COOKIE_SECURE=truewhen serving over HTTPS.
Deploy a production-ready version locally:
- Run the build command
npm run build- Start the production server
npm run startUse Docker Compose for a production stack with persistent storage for SQLite and uploads.
- Copy the Docker env template
cp .env.docker.example .env.docker- Adjust values in
.env.docker
NOVISLIDES_PORTexternal host portDATABASE_URLSQLite file path inside the container (default:file:/data/dev.db)GOOGLE_FONTS_API_KEYoptional fallback (Settings value has priority)EDITOR_AUTH_COOKIE_SECUREset totrueonly when served over HTTPSDEFAULT_PASSWORDinitial editor password fallback (passwordif omitted)
- Build and start
docker compose --env-file .env.docker up -d --build- Stop
docker compose --env-file .env.docker downNotes:
- Database is persisted in Docker volume
novislides_dbmounted at/data. - Media uploads are persisted in Docker volume
novislides_uploadsmounted at/app/public/uploads. - On startup, the container runs
prisma migrate deployautomatically before starting Next.js. - Default editor password follows
DEFAULT_PASSWORD(fallbackpassword) until changed in settings. - When using Playwright tests, ensure
E2E_EDITOR_PASSWORDin.envmatches the test configuration.
- Open
http://localhost:3000/edit - Create a slideshow (choose a template or accept the default)
- Add slides and elements
- Use Add Media to upload/select images or videos
- Mount a slideshow to a display from the left sidebar
- Unmount per slideshow or use Unmount All from the left sidebar
- Manage displays in Settings (name + resolution)
Use the Create Demo button in the editor. It creates a starter slideshow with sample content.
GET /showlist available display endpoints and direct slideshow endpointsGET /show/[slideshowId]specific slideshowGET /display/[name]slideshow mounted to that displayGET /helprenders markdown docs fromdocs/*.mdin an accordion
Markdown docs for /help live in docs/ and must start with a metadata block:
```meta
title: My Title
summary: Short description shown in the accordion.
icon: help
order: 10
```order and icon are optional.
Docs are sorted by order (ascending), then by title alphabetically.
Viewer pages subscribe to GET /api/events?slideshowId=...&screenKey=... and refresh when slideshow content changes. Display pages also subscribe to mount-change events for their display name and reload when remounted. If SSE fails, the viewer falls back to polling every ~15 seconds.
Note: the current EventHub is in-memory, so live refresh only works within a single server instance.
Templates are defined in lib/templates/templates.ts and are applied at creation.
Included templates:
- Default Starter (default): Title + subtitle, image + caption
- Fullscreen Image: Two full-bleed image slides
- Info Layout: Left text column, right image area
If you create a slideshow without choosing a template, the Default Starter is applied automatically.
New slideshows default to 1920 x 540. You can override this in the slideshow creation modal.
Supported formats:
- Images:
png,jpeg/jpg,gif,webp,svg - Videos:
mp4,webm
Media assets are stored under public/uploads/YYYY/MM/uuid.ext.
Video playback notes:
- Videos render in the viewer via a standard
<video>element. - Autoplay, loop, muted, and controls are configured per element. (however audio playback is limited due to policies set by modern browsers)
- Unit test (Vitest):
npm run test:unit - Playwright smoke test:
npm run test:e2e
app/routes (viewer, editor, API)components/viewer/editor UIdocs/help files (used by help viewer)lib/services, repositories, templates, validation, utilsprisma/schema and migrationspublic/assets and uploadstests/automated teststypes/shared types/interfaces
This project has been developed with the assistance of AI tools (notably GitHub Copilot and ChatGPT Codex) to help speed up development and provide code suggestions. While these tools can be helpful, they may also introduce code that is suboptimal, insecure, or incorrect. Which I experienced firsthand while reviewing and debugging the generated code. I don't blindly trust AI-generated code, and neither should you. Always review, test, and validate any code produced with the help of AI tools.
This project is licensed under the MIT License. See the LICENSE file for details
