Back to Blog
IoT Dashboard

Next.js IoT Analytics Dashboard: From Sensor Data to Production App

Next.js 14 with App Router is an excellent foundation for IoT analytics dashboards — server components eliminate data-fetching waterfalls, API routes connect to InfluxDB, and ISR keeps pages fast without stale data. Here's the complete architecture.

October 28, 2024
13 min read
Next.jsIoT DashboardInfluxDBApp Router

Next.js IoT Analytics Dashboard: From Sensor Data to Production App

Grafana is the go-to for operational monitoring. But when your IoT product needs a customer-facing analytics dashboard — branded, embedded in your web app, with custom business logic — Next.js 14 is the right foundation.

This guide builds a production IoT analytics dashboard with Next.js App Router, InfluxDB for time-series data, server components for performance, and real-time client components for live sensor readings.

Architecture

User Browser
  └── Next.js App Router
        ├── Server Components → InfluxDB (historical data)
        ├── Client Components → WebSocket (real-time data)
        └── API Routes → InfluxDB (dynamic queries)

InfluxDB ← Telegraf ← MQTT Broker ← IoT Devices

Project Setup

npx create-next-app@latest iot-analytics --typescript --tailwind --app
cd iot-analytics
npm install @influxdata/influxdb-client next-auth recharts

.env.local

INFLUX_URL=http://localhost:8086 INFLUX_TOKEN=your-influxdb-token INFLUX_ORG=codecaracal INFLUX_BUCKET=iot_telemetry NEXTAUTH_SECRET=your-secret NEXTAUTH_URL=http://localhost:3000 WS_URL=ws://localhost:8080

Step 1: InfluxDB Client Singleton

// lib/influx.ts
import { InfluxDB, QueryApi } from '@influxdata/influxdb-client'

let queryApi: QueryApi | null = null

export function getInfluxQueryApi(): QueryApi { if (!queryApi) { const client = new InfluxDB({ url: process.env.INFLUX_URL!, token: process.env.INFLUX_TOKEN!, }) queryApi = client.getQueryApi(process.env.INFLUX_ORG!) } return queryApi }

export async function queryInflux(flux: string): Promise { const api = getInfluxQueryApi() const rows: T[] = []

return new Promise((resolve, reject) => { api.queryRows(flux, { next: (row, meta) => rows.push(meta.toObject(row) as T), error: reject, complete: () => resolve(rows), }) }) }

Step 2: Device Overview — Server Component

Server components run on the server at request time. No client JS, no loading spinner for initial data:

// app/dashboard/page.tsx (Server Component)
import { queryInflux } from '@/lib/influx'
import { DeviceGrid } from '@/components/DeviceGrid'

interface DeviceRow { device_id: string _value: number _field: string _time: string }

async function getDeviceSummary() { const flux = ` from(bucket: "${process.env.INFLUX_BUCKET}") |> range(start: -1h) |> filter(fn: (r) => r._measurement == "device_telemetry") |> filter(fn: (r) => r._field == "temperature" or r._field == "humidity") |> group(columns: ["device_id", "_field"]) |> last() |> pivot(rowKey: ["device_id"], columnKey: ["_field"], valueColumn: "_value") `

return queryInflux<{ device_id: string temperature: number humidity: number _time: string }>(flux) }

export default async function DashboardPage() { const devices = await getDeviceSummary()

return (

Fleet Overview

) }

Step 3: API Routes for Dynamic Queries

Client components and external consumers fetch data via API routes:

// app/api/devices/[deviceId]/history/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { queryInflux } from '@/lib/influx'
import { getServerSession } from 'next-auth'

export async function GET( req: NextRequest, { params }: { params: { deviceId: string } } ) { // Auth check — only device owners can query their devices const session = await getServerSession() if (!session) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })

const { searchParams } = new URL(req.url) const range = searchParams.get('range') ?? '-24h' const field = searchParams.get('field') ?? 'temperature'

// Validate inputs to prevent Flux injection const validRanges = ['-1h', '-6h', '-24h', '-7d', '-30d'] const validFields = ['temperature', 'humidity', 'voltage', 'pressure'] if (!validRanges.includes(range)) return NextResponse.json({ error: 'Invalid range' }, { status: 400 }) if (!validFields.includes(field)) return NextResponse.json({ error: 'Invalid field' }, { status: 400 })

const flux = ` from(bucket: "${process.env.INFLUX_BUCKET}") |> range(start: ${range}) |> filter(fn: (r) => r._measurement == "device_telemetry") |> filter(fn: (r) => r._field == "${field}") |> filter(fn: (r) => r.device_id == "${params.deviceId}") |> aggregateWindow(every: 5m, fn: mean, createEmpty: false) `

const data = await queryInflux<{ _time: string; _value: number }>(flux) return NextResponse.json(data) }

Step 4: Real-Time Client Component

// components/LiveSensorCard.tsx (Client Component)
'use client'

import { useState, useEffect } from 'react' import { LineChart, Line, YAxis, ResponsiveContainer } from 'recharts'

interface Props { deviceId: string initialTemp: number initialHumid: number }

export function LiveSensorCard({ deviceId, initialTemp, initialHumid }: Props) { const [temp, setTemp] = useState(initialTemp) const [humid, setHumid] = useState(initialHumid) const [history, setHistory] = useState<{ t: number; temp: number }[]>([]) const [online, setOnline] = useState(true)

useEffect(() => { const ws = new WebSocket(${process.env.NEXT_PUBLIC_WS_URL}/device/${deviceId})

ws.onmessage = (event) => { const data = JSON.parse(event.data) if (data.type === 'telemetry') { setTemp(data.payload.temperature) setHumid(data.payload.humidity) setOnline(true) setHistory(prev => [ ...prev.slice(-59), { t: Date.now(), temp: data.payload.temperature } ]) } }

ws.onclose = () => setOnline(false) return () => ws.close() }, [deviceId])

return (

{deviceId} {online ? '● Live' : '○ Offline'}
{temp.toFixed(1)}°
Temperature
{humid.toFixed(0)}%
Humidity
) }

Step 5: Authentication with NextAuth

// app/api/auth/[...nextauth]/route.ts
import NextAuth from 'next-auth'
import CredentialsProvider from 'next-auth/providers/credentials'

const handler = NextAuth({ providers: [ CredentialsProvider({ credentials: { email: { label: 'Email', type: 'email' }, password: { label: 'Password', type: 'password' }, }, async authorize(credentials) { const user = await validateUser(credentials!.email, credentials!.password) if (!user) return null return { id: user.id, email: user.email, deviceIds: user.deviceIds, } }, }), ], callbacks: { async jwt({ token, user }) { if (user) token.deviceIds = (user as any).deviceIds return token }, async session({ session, token }) { (session.user as any).deviceIds = token.deviceIds return session }, }, pages: { signIn: '/login' }, })

export { handler as GET, handler as POST }

Incremental Static Regeneration for Analytics Pages

For historical analytics pages that don't need real-time data:

// app/analytics/[deviceId]/page.tsx
export const revalidate = 300  // Regenerate every 5 minutes

export default async function AnalyticsPage({ params }) { const data = await get24hSummary(params.deviceId) return }

ISR serves pre-generated pages instantly while keeping data fresh — no spinner, no loading state, just fast pages.

Want a customer-facing IoT analytics dashboard built for your product? [Contact Code Caracal](/contact) — we build branded Next.js IoT dashboards used by enterprise clients across 15+ countries.

Written by CodeCaracal Engineering

We write from production experience — every technique in our articles has been deployed to real clients. No academic theory.

More Articles

Business · 12 min read

IoT Device Compliance: FCC, CE, and Product Certification Guide for Hardware Startups

Business · 11 min read

What to Look for When Hiring an IoT Development Partner: 8 Critical Criteria

Business · 11 min read

IoT MVP to Production: Realistic Timeline and Budget for Hardware Startups

Business · 11 min read

IoT Development Agency vs Building In-House: A Decision Framework for Founders

Business · 11 min read

How Much Does It Cost to Build an IoT Product in 2024? A Realistic Breakdown

IoT Dashboard · 11 min read

IoT Dashboard UX: Design Principles for Industrial Monitoring Interfaces

IoT Dashboard · 12 min read

Node.js WebSocket Server: The Real-Time Backend for IoT Dashboards

Cloud & DevOps · 12 min read

Containerizing IoT Backend Services with Docker: From Dev to Production

IoT Dashboard · 14 min read

Grafana + InfluxDB IoT Monitoring: Complete Production Setup Guide

IoT Dashboard · 12 min read

Building Real-Time IoT Dashboards with React and Recharts

Cloud & DevOps · 13 min read

CI/CD for Embedded Firmware: Automated Build, Test, and OTA Release Pipeline

Mobile Development · 12 min read

Flutter Offline-First IoT Apps: Hive + Sync Architecture That Works in the Field

Cloud & DevOps · 14 min read

Terraform for IoT Infrastructure: Provisioning AWS IoT Core, Lambda, and InfluxDB as Code

Mobile Development · 10 min read

Flutter IoT Alerts: Firebase Push Notifications for Device Events

Cloud & DevOps · 12 min read

Deploying IoT Backends on AWS: ECS Fargate vs Lambda vs EC2 Decision Guide

Mobile Development · 11 min read

Flutter + MQTT: Building Production IoT Mobile Apps That Scale

Mobile Development · 13 min read

Flutter BLE: Building a Bluetooth IoT Controller App from Scratch

Cloud & DevOps · 13 min read

AWS IoT Core vs Azure IoT Hub vs Google Cloud IoT: 2024 Honest Comparison

IoT Engineering · 13 min read

Kafka vs RabbitMQ for IoT: Choosing the Right Message Queue for High-Volume Telemetry

IoT Engineering · 14 min read

IoT System Testing: Unit, Integration, Hardware-in-the-Loop, and End-to-End

IoT Engineering · 14 min read

Predictive Maintenance with IoT Sensor Data: From Threshold to Machine Learning

Embedded Systems · 14 min read

IoT Bootloader Design: Secure Boot, A/B Partitions, and Reliable OTA Recovery

IoT Engineering · 14 min read

Multi-Tenant IoT Platform Architecture: Isolation, Scaling, and Data Partitioning

Embedded Systems · 14 min read

Memory Management in Embedded Firmware: Avoiding Heap Fragmentation and Stack Overflows

IoT Engineering · 13 min read

IoT Cost Optimization: How We Cut AWS IoT Bills by 60% Without Sacrificing Reliability

IoT Engineering · 12 min read

Edge Computing in IoT: When to Process On-Device vs In the Cloud

IoT Engineering · 13 min read

Digital Twins for IoT: Building a Virtual Mirror of Your Physical Devices

Embedded Systems · 14 min read

ESP32 Deep Sleep Mastery: Cutting Power Consumption from 240mA to 10µA

IoT Engineering · 10 min read

MQTT QoS 0, 1, and 2 Explained: Choosing the Right Level for IoT

IoT Engineering · 14 min read

IoT Monitoring and Observability: Metrics, Logs, and Distributed Tracing

Embedded Systems · 14 min read

Debugging Embedded Firmware: JTAG, GDB, Logic Analyzers, and Serial Tracing

IoT Engineering · 12 min read

WebSocket vs MQTT vs Server-Sent Events: Real-Time IoT Protocol Deep Dive

Embedded Systems · 13 min read

STM32 HAL vs Low-Level Drivers: When the Abstraction Costs You Too Much

IoT Engineering · 13 min read

IoT Data Pipeline: From Raw Sensor Reading to Live Dashboard in Under 100ms

IoT Engineering · 13 min read

Zero-Touch IoT Device Provisioning: Scaling from 10 to 100,000 Devices

Embedded Systems · 13 min read

UART vs SPI vs I2C: Choosing the Right Protocol for Sensor Integration

IoT Engineering · 12 min read

Real-Time IoT Alerting: From Simple Thresholds to ML Anomaly Detection

Embedded Systems · 12 min read

ESP32 Partition Table: Designing Flash Layout for Production Firmware

IoT Engineering · 12 min read

IoT Architecture Patterns: Hub-and-Spoke, Mesh, and Edge-Cloud Hybrid

Embedded Systems · 13 min read

IoT Battery Life Optimization: Engineering Devices That Last Years on a Single Charge

IoT Engineering · 13 min read

Time-Series Databases for IoT: InfluxDB vs TimescaleDB vs AWS Timestream

Security · 14 min read

Zero-Trust Security for Embedded IoT: Why Your Devices Are Probably Vulnerable

Embedded Systems · 14 min read

FreeRTOS on ESP32: Task Scheduling, Queues, and Resource Management for IoT

IoT Engineering · 12 min read

Building a Production IoT Gateway with Raspberry Pi and Node.js

Embedded Systems · 13 min read

ESP32 vs STM32: Choosing the Right Microcontroller for Your IoT Project

Mobile Development · 10 min read

Flutter + WebSocket: Building Real-Time IoT Dashboards That Don't Stutter

IoT Engineering · 13 min read

IoT Fleet Management at Scale: AWS IoT Core Device Registry and Provisioning

IoT Engineering · 11 min read

MQTT vs HTTP for IoT: Which Protocol Wins in Production?

IoT Engineering · 12 min read

ESP32 → MQTT → AWS IoT Core: The Production-Grade Architecture Guide

Let's Build Together

Got an IoT challenge?
We've shipped it.

Whether you need a fleet to track, a factory to monitor, or a farm to automate — our team has done it before and we'd love to build it with you. Typical response time: under 24 hours.

No upfront commitment99.9% uptime SLANDA on requestFixed-price options