- Overview
- Features
- Quick Start
- How It Works
- Installation
- Usage
- Troubleshooting
- Contributing
- Community
BYOS (Build Your Own Server) Next.js is a community-maintained library for the TRMNL device, designed to provide a flexible and customizable server solution. This Next.js implementation offers a robust, modern approach to device management and display generation.
live demo: https://byos-nextjs.vercel.app/
- π§ Customizable device management
- πΌοΈ Dynamic screen generation
- π Easy deployment to Vercel
- π Comprehensive logging system
- π Secure API key management
- π» Modern tech stack (Next.js 15, React 19, Tailwind CSS v4)
- π§Ή Clean, standardized codebase with Biome for formatting
β οΈ Using a canary version of Shadcn for Tailwind v4 support; be cautious with AI-generated code.
This project is in the Alpha stage. Here's our development roadmap:
- β Core functionality for device management
- β Dynamic screen generation
- β Supabase integration
- β Examples framework
- β Codebase refactoring and standardization
- β Improved initialization flow (2025-03-11)
- β "No database" mode for simpler deployments (2025-03-11)
- π More pixelated fonts
- π More template examples
- π MySQL/local file support
- π Demo mode for testing without affecting production devices
- π Enhanced documentation
- π§ͺ Testing framework
- π Advanced authentication options
If you encounter any problems:
- GitHub Issues: Open an issue on our GitHub repository
- Email: Send details to [email protected]
- Discussions: Reply to my message in the TRMNL Discord server
- Click the Vercel deployment button
- Link a free Supabase database
- Follow the deployment instructions
- Open the deployed app and initialize the database tables
- Point your device to the deployed app (see How It Works for details)
Note for local development: once setup, sync enviroment variables to your local development by:
- go to https://supabase.com/dashboard/project/_/settings/integrations
- if not linked already, link your supabase project to vercel
- under Vercel Integration, find "manage", turn on "preview" and "development", and then "Resync environment variables"
- now using
vercel link
andvercel env pull
, you should see these environment variables in your local.env.local
file:
NEXT_PUBLIC_SUPABASE_ANON_KEY
NEXT_PUBLIC_SUPABASE_URL
POSTGRES_DATABASE
POSTGRES_HOST
POSTGRES_PASSWORD
POSTGRES_PRISMA_URL
POSTGRES_URL
POSTGRES_URL_NON_POOLING
POSTGRES_USER
SUPABASE_ANON_KEY
SUPABASE_JWT_SECRET
SUPABASE_SERVICE_ROLE_KEY
SUPABASE_URL
- Node.js (v20 or later)
- pnpm, npm, or yarn
- Git
# Clone the repository
git clone https://github.com/usetrmnl/byos_next
cd byos-nextjs
# Install dependencies
pnpm install # or npm install or yarn install
Set up a Supabase account and add these environment variables to your .env.local
file:
NEXT_PUBLIC_SUPABASE_URL
NEXT_PUBLIC_SUPABASE_ANON_KEY
Manually initialize the database tables in your Supabase SQL editor:
-- Enable UUID generation extension
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- Devices Table
CREATE TABLE public.devices (
id BIGSERIAL PRIMARY KEY,
friendly_id VARCHAR NOT NULL UNIQUE,
name VARCHAR NOT NULL,
mac_address VARCHAR NOT NULL UNIQUE,
api_key VARCHAR NOT NULL UNIQUE,
screen VARCHAR NULL DEFAULT NULL,
refresh_schedule JSONB NULL,
timezone TEXT NOT NULL DEFAULT 'UTC',
last_update_time TIMESTAMPTZ NULL,
next_expected_update TIMESTAMPTZ NULL,
last_refresh_duration INTEGER NULL,
battery_voltage NUMERIC NULL,
firmware_version TEXT NULL,
rssi INTEGER NULL,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
-- Indexes for Devices
CREATE INDEX idx_devices_refresh_schedule ON public.devices USING GIN (refresh_schedule);
-- Logs Table
CREATE TABLE public.logs (
id BIGSERIAL PRIMARY KEY,
device_id BIGINT NOT NULL,
friendly_id TEXT NULL,
log_data TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT logs_friendly_id_fkey FOREIGN KEY (friendly_id) REFERENCES public.devices (friendly_id)
);
-- System Logs Table
CREATE TABLE public.system_logs (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
created_at TIMESTAMPTZ DEFAULT now(),
level VARCHAR NOT NULL,
message TEXT NOT NULL,
source VARCHAR NULL,
metadata TEXT NULL,
trace TEXT NULL
);
-- Indexes for System Logs
CREATE INDEX idx_system_logs_created_at ON public.system_logs (created_at);
CREATE INDEX idx_system_logs_level ON public.system_logs (level);
Start the development server:
# Start development server
pnpm run dev # or npm run dev or yarn run dev
This project uses Biome for code formatting. To format your code:
# Format code
pnpm lint
When dealing with AI-generated code, be aware that Tailwind v4 has some syntax differences. Use the following command to add new Shadcn components:
pnpm dlx shadcn@canary add [component1] [component2] [component3]
The BYOS architecture provides three main endpoints for device interaction:
- Purpose: Device registration and API key generation
- Usage: Called when a device is reset or lacks an API key
- Request Flow:
- Device sends MAC address in request headers
- Server checks if the device exists in the database
- If new, generates a unique API key and friendly device ID
- Returns API key to the device for future authentication
curl -X GET http://[YOUR BASE URL]/api/setup \
-H "Content-Type: application/json" \
-H "ID: 12:34:56:78:9A:BC"
Response Example:
{
"status": 200,
"api_key": "uniqueApiKeyGenerated",
"friendly_id": "DEVICE_ABC123",
"message": "Device successfully registered"
}
- Purpose: Primary endpoint for screen content delivery
- Usage: Called repeatedly by the device after setup to get new screens
- Key Functions:
- Provides the URL for the next screen to display
- Specifies how long the device should sleep before requesting again
- Can optionally signal firmware reset/update requirements
curl -X GET http://[YOUR BASE URL]/api/display \
-H "Content-Type: application/json" \
-H "ID: 12:34:56:78:9A:BC" \
-H "Access-Token: uniqueApiKey"
Response Example:
{
"status": 0,
"image_url": "https://your-base-url/api/bitmap/DEVICE_ID_TIMESTAMP.bmp",
"filename": "DEVICE_ID_TIMESTAMP.bmp",
"refresh_rate": 180,
"reset_firmware": false,
"update_firmware": false
}
Note: This implementation does not currently handle button functionality.
- Purpose: Error and issue reporting
- Usage: Called when errors or issues occur on the device
- Behavior: Logs are stored in the Supabase database for troubleshooting
Unlike the official Ruby/Python implementations, this Next.js implementation:
- Generates screens on-demand: When a device requests a display update
- Leverages Next.js caching: Uses built-in caching mechanisms for performance
- Dynamic BMP generation: The bitmap URL is a dynamic API endpoint
- Efficient revalidation:
- First request may take longer to generate the screen
- Subsequent requests are served from cache while revalidating in the background
- This approach provides both speed and fresh content
-
Image Format: 800x480 pixel 1-bit bitmap (.bmp)
-
Rendering: Uses Satori for dynamic image generation Rendering pipeline: JSX component -> pre-satori wrapper -> satori (svg) -> vercel image response (png) -> jimp (bmp) -> fixed header to fit TRMNL display
-
Caching Strategy: 60-second revalidation window
Important: This implementation does not include a comprehensive authentication system.
- No user management: Unlike some official implementations, there is no built-in user field in the database
- Basic device authentication: Only verifies device MAC address and API key
- Production deployment recommendations:
- Implement your own authentication layer (e.g., NextAuth, SupabaseAuth)
- Use middleware for request validation
- Update the database schema to include user management if needed
- Consider rate limiting and other security measures
The project includes an examples section to visualize and test components in both direct rendering and bitmap (BMP) rendering forms. This helps develop and test components for the TRMNL device.
Visit [base url]/examples
to view the examples page.
To set up your own screen example, use the following structure:
- Create your component folder in the
app/examples/screens
directory following any existing examples. - Add your component and data fetching logic
- Add an entry to
app/examples/screens.json
Each screen is defined in app/examples/screens.json
and can be accessed via its slug.
This allows you to code and preview before pointing your device to any of the screens.
We welcome contributions! Please see our Contributing Guidelines for details on:
- Reporting bugs
- Suggesting features
- Submitting pull requests
- Report issues on GitHub
- Submit pull requests
- Improve documentation
- Share use cases and feedback
- π’ GitHub Discussions
- π¦ Twitter @usetrmnl
- π¬ Join our community channels
This project is open-source and available under the MIT License.
Happy Coding! π