Building a Family Electronic Bulletin Board with Next.js

How I built a full-featured family bulletin board with Next.js, SQLite, and Tailwind CSS - perfect for a mud room kiosk display

Building a Family Electronic Bulletin Board with Next.js

After years of thinking about it (since 2019!), I finally built a comprehensive family electronic bulletin board. It's displayed on a large LCD screen in our mud room, giving everyone a quick glance at what's happening in our lives.

The Vision

I wanted a centralized place where our whole family could see:

  • Upcoming events and appointments - Never miss a soccer game or doctor's appointment
  • To-do lists - Shared family tasks with priority levels
  • Grocery list - Real-time sync so anyone can add items
  • Weather - Quick glance at today's forecast
  • Important notes - WiFi passwords, emergency contacts, etc.
  • Countdowns - Excitement for upcoming vacations or events
  • Birthday reminders - Never forget a family birthday
  • Family messages - Quick announcements and celebrations
  • Affirmations - Daily inspiration and motivation

Tech Stack

I built this as part of my existing Next.js app, keeping everything in one codebase:

  • Next.js 16 with App Router - Server and client components
  • SQLite (better-sqlite3) - Local database, simple and fast
  • Tailwind CSS 4 - Responsive styling with custom color theme
  • Clerk - Authentication for admin pages
  • Open-Meteo API - Free weather data
  • TypeScript - Type safety throughout

Architecture

Database Schema

The bulletin board uses 9 core tables in SQLite:

  1. bulletin_events - Calendar events with optional Google Calendar sync
  2. bulletin_todos - Task management with priority and assignment
  3. bulletin_grocery_items - Organized by category (produce, dairy, etc.)
  4. bulletin_notes - Permanent information (WiFi, contacts, etc.)
  5. bulletin_countdowns - Event countdowns with custom icons
  6. bulletin_birthdays - Auto-calculates days until next birthday
  7. bulletin_affirmations - Rotating daily inspiration
  8. bulletin_meals - Weekly meal planning
  9. bulletin_chores - Task assignment with frequency tracking
  10. bulletin_messages - Family announcements with expiration dates

Each table includes:

  • user_id for tracking who created items
  • Timestamps (created_at, updated_at)
  • Indexes on frequently queried columns
  • SQLite CHECK constraints for enum values

API Endpoints

Following RESTful patterns:

Public (No Auth):

  • GET /api/bulletin/kiosk - Aggregates all data for kiosk display
  • GET /api/bulletin/weather - Proxies Open-Meteo API

Protected (Clerk Auth):

  • GET/POST /api/bulletin/events - List and create events
  • GET/PATCH/DELETE /api/bulletin/events/[id] - Manage individual events
  • GET/POST /api/bulletin/todos - List and create todos
  • GET/PATCH/DELETE /api/bulletin/todos/[id] - Manage individual todos
  • GET/POST /api/bulletin/grocery - List and create grocery items
  • GET/PATCH/DELETE /api/bulletin/grocery/[id] - Manage individual items

All endpoints follow the same pattern:

  • Validate authentication with Clerk
  • Validate request data
  • Use database helper functions from lib/db.ts
  • Return consistent JSON responses
  • Proper HTTP status codes (400, 401, 404, 500)

Kiosk Display

The kiosk page (/bulletin) is public - no authentication required. It:

  • Fetches all data from /api/bulletin/kiosk aggregation endpoint
  • Auto-refreshes every 30 seconds
  • Updates clock every second for real-time feel
  • Uses responsive grid layout (1-3 columns based on screen size)
  • Shows/hides widgets based on data availability

Widget Architecture:

I created a reusable WidgetContainer component that provides:

  • Consistent header styling with gradient background
  • Optional icons and loading states
  • Error handling
  • Standardized padding and spacing

Individual widgets:

  • EventsWidget - Upcoming events with dates, times, locations
  • TodosWidget - Active tasks with priority color coding
  • GroceryWidget - Items grouped by category
  • WeatherWidget - Current temp, high/low, weather icons
  • MessagesWidget - Family announcements with priority levels
  • CountdownWidget - Days until events with custom icons
  • BirthdaysWidget - Upcoming birthdays with "TODAY!" highlighting

Admin Interface

The admin section (/admin/bulletin) requires authentication:

  • Dashboard shows quick stats and links to management pages
  • Todo management page (/admin/bulletin/todos) has:
    • List view with checkboxes to mark complete
    • Inline form for creating new todos
    • Priority indicators (red/orange/gray)
    • Delete functionality
    • Real-time updates after actions

Following the same pattern, I can easily add admin pages for:

  • Events management
  • Grocery list management
  • Notes/settings
  • Message board
  • Photo uploads

Key Features Implemented

1. Smart Todo Management

  • Priority levels (high/medium/low) with color coding
  • Assignment to specific family members
  • Due dates with visual indicators
  • Checkbox completion with timestamp tracking
  • Filters for active vs. completed tasks

2. Real-Time Weather

  • Open-Meteo API (free, no API key required)
  • Current temperature and conditions
  • High/low for the day
  • Weather icon mapping for visual clarity
  • Auto-refresh every 10 minutes

3. Category-Based Grocery List

  • Organized by category (produce, dairy, meat, etc.)
  • Quantity tracking
  • Optional notes per item
  • Checkbox to mark items as obtained
  • Unchecked items display on kiosk

4. Event Calendar

  • Start/end datetime support
  • All-day event option
  • Location field
  • Custom color per event
  • "Today" highlighting
  • Ready for Google Calendar sync (future enhancement)

5. Birthday Countdown

  • Calculates days until next birthday
  • Handles year rollovers correctly
  • Special "TODAY!" badge for today's birthdays
  • "Tomorrow" indicator
  • Relationship field (family/friend)

6. Family Messages

  • Priority levels (low/normal/high)
  • Message types (announcement/reminder/celebration)
  • Expiration dates
  • Author name display
  • Icon mapping based on type
  • Auto-hides after expiration

7. Countdown Timers

  • Custom event countdowns
  • Optional emoji/icon
  • Custom colors
  • Description field
  • Days until calculation
  • Hides after event passes

Code Patterns

Database Helper Functions

All database operations are centralized in lib/db.ts:

export function getBulletinTodos(completed?: boolean) {
  const db = getDatabase();
  let query = 'SELECT * FROM bulletin_todos WHERE 1=1';
  const params: any[] = [];
 
  if (completed !== undefined) {
    query += ' AND completed = ?';
    params.push(completed ? 1 : 0);
  }
 
  query += ` ORDER BY
    CASE priority
      WHEN 'high' THEN 1
      WHEN 'medium' THEN 2
      ELSE 3
    END,
    due_date ASC NULLS LAST,
    sort_order ASC`;
 
  return db.prepare(query).all(...params);
}

This pattern:

  • Provides type-safe access to database
  • Handles SQL parameter binding
  • Includes smart sorting (priority, then due date)
  • Easy to test and maintain

API Route Pattern

Every API route follows this structure:

export async function POST(request: NextRequest) {
  try {
    // 1. Check authentication
    const { userId } = await auth();
    if (!userId) {
      return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
    }
 
    // 2. Parse and validate request body
    const body = await request.json();
    const { title, description, priority, due_date } = body;
 
    if (!title) {
      return NextResponse.json(
        { error: "Title is required" },
        { status: 400 }
      );
    }
 
    // 3. Call database helper function
    const id = createBulletinTodo({
      user_id: userId,
      title,
      description,
      priority: priority || "medium",
      due_date,
    });
 
    // 4. Return success response
    return NextResponse.json({ ...todo, id }, { status: 201 });
  } catch (error: any) {
    console.error("Create todo error:", error);
    return NextResponse.json(
      { error: error.message || "Failed to create todo" },
      { status: 500 }
    );
  }
}

Widget Component Pattern

Widgets are simple, focused React components:

interface TodosWidgetProps {
  todos: BulletinTodo[];
}
 
export default function TodosWidget({ todos }: TodosWidgetProps) {
  if (!todos || todos.length === 0) {
    return (
      <WidgetContainer title="To-Do List" icon="✓">
        <p className="text-slate-500 text-center py-4">No active tasks</p>
      </WidgetContainer>
    );
  }
 
  return (
    <WidgetContainer title="To-Do List" icon="✓">
      <div className="space-y-3">
        {todos.map((todo) => (
          // Render todo item...
        ))}
      </div>
    </WidgetContainer>
  );
}

This keeps widgets:

  • Independent and reusable
  • Easy to show/hide based on data
  • Consistent styling via WidgetContainer
  • Simple to test

Deployment Considerations

For a kiosk display, you'll want to:

  1. Set up the display device

    • Use a Raspberry Pi, old laptop, or dedicated display
    • Configure auto-start of browser in kiosk mode
    • Disable sleep/screensaver
    • Set up auto-refresh or use SSE for real-time updates
  2. Network considerations

    • Ensure stable WiFi connection
    • Consider static IP for easy access
    • Optionally restrict bulletin board to home network
  3. Browser settings

    • Use Chrome/Firefox kiosk mode
    • Hide scrollbars with CSS
    • Disable context menus
    • Prevent accidental navigation
  4. Database backups

    • SQLite database is a single file (data/app.db)
    • Easy to backup with cron job
    • Consider daily snapshots

Future Enhancements

The foundation is now in place for many enhancements:

  1. Google Calendar Sync - Automatically pull in family events
  2. Photo Slideshow - Rotate through family photos
  3. Meal Planning - Weekly meal calendar linked to grocery list
  4. Chore Rotation - Automatic chore assignments
  5. Real-time Updates - Server-Sent Events for instant updates
  6. Mobile Companion App - Native mobile UI for easier data entry
  7. Voice Integration - "Hey Google, add milk to grocery list"
  8. QR Code WiFi - Generate QR code for guest WiFi access
  9. Transit Info - Show commute times or bus schedules
  10. Package Tracking - Amazon/UPS delivery notifications

Lessons Learned

Keep it simple: I resisted the urge to build everything at once. Starting with todos, events, and grocery list gave me a working MVP. Other features can be added incrementally.

Separation of concerns: Database functions, API routes, and UI components are all separate. This makes testing and extending much easier.

Use existing infrastructure: Building this into my existing Next.js app meant I didn't need to set up new hosting, authentication, or deployment. It just works.

SQLite is underrated: For a family app with a few users, SQLite is perfect. No separate database server to maintain, backups are simple file copies, and it's blazing fast.

Public kiosk, authenticated admin: This pattern works great. Anyone in the family can see the bulletin board, but only authenticated users can manage content.

Conclusion

After 6 years of having this on my todo list, it's incredibly satisfying to finally have a working family bulletin board. The kids love seeing their tasks and upcoming events, and we use the grocery list constantly.

Total implementation time: ~4 hours Lines of code: ~2,500 Database tables: 10 API endpoints: 12 React components: 10

The best part? It's all just TypeScript, React, and SQL - technologies I already know. No new frameworks to learn, no complicated setup. Just good old web development solving a real family problem.

Now every time we walk through the mud room, we're informed, organized, and connected.

Try it yourself: Visit /bulletin to see the live demo, or check out the admin panel if you're logged in.


Built with Claude Code in December 2025