Next.js On The Pi
step 1:
# installs nvm (Node Version Manager)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
# download and install Node.js (you may need to restart the terminal)
nvm install 20
# verifies the right Node.js version is in the environment
node -v # should print `v20.15.1`
# verifies the right npm version is in the environment
npm -v # should print `10.7.0`
# installs nvm (Node Version Manager)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
# download and install Node.js (you may need to restart the terminal)
nvm install 20
# verifies the right Node.js version is in the environment
node -v # should print `v20.15.1`
# verifies the right npm version is in the environment
npm -v # should print `10.7.0`
# installs nvm (Node Version Manager)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
# download and install Node.js (you may need to restart the terminal)
nvm install 20
# verifies the right Node.js version is in the environment
node -v # should print `v20.15.1`
# verifies the right npm version is in the environment
npm -v # should print `10.7.0`
# installs nvm (Node Version Manager)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
# download and install Node.js (you may need to restart the terminal)
nvm install 20
# verifies the right Node.js version is in the environment
node -v # should print `v20.15.1`
# verifies the right npm version is in the environment
npm -v # should print `10.7.0`
I just got that script straight from node.js: https://nodejs.org/en/download/package-manager
step 2:
npx create-next-app@latest
npx create-next-app@latest
npx create-next-app@latest
npx create-next-app@latest
You can play around with this as is using npm run dev
and visiting pi-host-name:3000 in your browser. The next instructions will just setup a basic page to view stats about the pi.
step 3:
npx shadcn-ui@latest init
npx shadcn-ui@latest init
npx shadcn-ui@latest init
npx shadcn-ui@latest init
npx shadcn-ui@latest add card progress
npx shadcn-ui@latest add card progress
npx shadcn-ui@latest add card progress
npx shadcn-ui@latest add card progress
step 4:
src/app/stats/page.tsx
import { getSystemDetails } from "@/lib/system";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Progress } from "@/components/ui/progress";
export default async function Home() {
const systemInfo = await getSystemDetails();
return (
<main className="min-h-screen bg-background flex flex-col items-center justify-center p-6">
<h1 className="text-3xl font-bold mb-6 text-foreground">Raspberry Pi</h1>
<Card className="w-full max-w-md">
<CardHeader>
<CardTitle>System Information</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
{[
["Hostname", systemInfo.os.hostname()],
["Platform", systemInfo.os.platform()],
["Architecture", systemInfo.os.arch()],
["CPU Temperature", `${systemInfo.cpuTemp.toFixed(1)}°C`],
].map(([label, value]) => (
<div key={label} className="flex justify-between text-sm">
<span className="text-muted-foreground">{label}:</span>
<span className="text-foreground font-medium">{value}</span>
</div>
))}
</div>
<div className="space-y-2">
<h3 className="text-lg font-semibold text-foreground">CPU Usage</h3>
{systemInfo.cpuUsage.map((usage, index) => (
<div key={index} className="space-y-1">
<div className="flex justify-between text-sm text-muted-foreground">
<span>Core {index}</span>
<span>{usage}%</span>
</div>
<Progress value={parseFloat(usage)} className="h-2" />
</div>
))}
</div>
<div className="space-y-2">
<h3 className="text-lg font-semibold text-foreground">Memory Usage</h3>
<div className="flex justify-between text-sm text-muted-foreground">
<span>Used</span>
<span>{systemInfo.memoryUsage.used.toFixed(2)} / {systemInfo.memoryUsage.total.toFixed(2)} GB</span>
</div>
<Progress
value={(systemInfo.memoryUsage.used / systemInfo.memoryUsage.total) * 100}
className="h-2"
/>
</div>
</CardContent>
</Card>
</main>
);
}
src/app/stats/page.tsx
import { getSystemDetails } from "@/lib/system";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Progress } from "@/components/ui/progress";
export default async function Home() {
const systemInfo = await getSystemDetails();
return (
<main className="min-h-screen bg-background flex flex-col items-center justify-center p-6">
<h1 className="text-3xl font-bold mb-6 text-foreground">Raspberry Pi</h1>
<Card className="w-full max-w-md">
<CardHeader>
<CardTitle>System Information</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
{[
["Hostname", systemInfo.os.hostname()],
["Platform", systemInfo.os.platform()],
["Architecture", systemInfo.os.arch()],
["CPU Temperature", `${systemInfo.cpuTemp.toFixed(1)}°C`],
].map(([label, value]) => (
<div key={label} className="flex justify-between text-sm">
<span className="text-muted-foreground">{label}:</span>
<span className="text-foreground font-medium">{value}</span>
</div>
))}
</div>
<div className="space-y-2">
<h3 className="text-lg font-semibold text-foreground">CPU Usage</h3>
{systemInfo.cpuUsage.map((usage, index) => (
<div key={index} className="space-y-1">
<div className="flex justify-between text-sm text-muted-foreground">
<span>Core {index}</span>
<span>{usage}%</span>
</div>
<Progress value={parseFloat(usage)} className="h-2" />
</div>
))}
</div>
<div className="space-y-2">
<h3 className="text-lg font-semibold text-foreground">Memory Usage</h3>
<div className="flex justify-between text-sm text-muted-foreground">
<span>Used</span>
<span>{systemInfo.memoryUsage.used.toFixed(2)} / {systemInfo.memoryUsage.total.toFixed(2)} GB</span>
</div>
<Progress
value={(systemInfo.memoryUsage.used / systemInfo.memoryUsage.total) * 100}
className="h-2"
/>
</div>
</CardContent>
</Card>
</main>
);
}
src/app/stats/page.tsx
import { getSystemDetails } from "@/lib/system";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Progress } from "@/components/ui/progress";
export default async function Home() {
const systemInfo = await getSystemDetails();
return (
<main className="min-h-screen bg-background flex flex-col items-center justify-center p-6">
<h1 className="text-3xl font-bold mb-6 text-foreground">Raspberry Pi</h1>
<Card className="w-full max-w-md">
<CardHeader>
<CardTitle>System Information</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
{[
["Hostname", systemInfo.os.hostname()],
["Platform", systemInfo.os.platform()],
["Architecture", systemInfo.os.arch()],
["CPU Temperature", `${systemInfo.cpuTemp.toFixed(1)}°C`],
].map(([label, value]) => (
<div key={label} className="flex justify-between text-sm">
<span className="text-muted-foreground">{label}:</span>
<span className="text-foreground font-medium">{value}</span>
</div>
))}
</div>
<div className="space-y-2">
<h3 className="text-lg font-semibold text-foreground">CPU Usage</h3>
{systemInfo.cpuUsage.map((usage, index) => (
<div key={index} className="space-y-1">
<div className="flex justify-between text-sm text-muted-foreground">
<span>Core {index}</span>
<span>{usage}%</span>
</div>
<Progress value={parseFloat(usage)} className="h-2" />
</div>
))}
</div>
<div className="space-y-2">
<h3 className="text-lg font-semibold text-foreground">Memory Usage</h3>
<div className="flex justify-between text-sm text-muted-foreground">
<span>Used</span>
<span>{systemInfo.memoryUsage.used.toFixed(2)} / {systemInfo.memoryUsage.total.toFixed(2)} GB</span>
</div>
<Progress
value={(systemInfo.memoryUsage.used / systemInfo.memoryUsage.total) * 100}
className="h-2"
/>
</div>
</CardContent>
</Card>
</main>
);
}
src/app/stats/page.tsx
import { getSystemDetails } from "@/lib/system";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Progress } from "@/components/ui/progress";
export default async function Home() {
const systemInfo = await getSystemDetails();
return (
<main className="min-h-screen bg-background flex flex-col items-center justify-center p-6">
<h1 className="text-3xl font-bold mb-6 text-foreground">Raspberry Pi</h1>
<Card className="w-full max-w-md">
<CardHeader>
<CardTitle>System Information</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
{[
["Hostname", systemInfo.os.hostname()],
["Platform", systemInfo.os.platform()],
["Architecture", systemInfo.os.arch()],
["CPU Temperature", `${systemInfo.cpuTemp.toFixed(1)}°C`],
].map(([label, value]) => (
<div key={label} className="flex justify-between text-sm">
<span className="text-muted-foreground">{label}:</span>
<span className="text-foreground font-medium">{value}</span>
</div>
))}
</div>
<div className="space-y-2">
<h3 className="text-lg font-semibold text-foreground">CPU Usage</h3>
{systemInfo.cpuUsage.map((usage, index) => (
<div key={index} className="space-y-1">
<div className="flex justify-between text-sm text-muted-foreground">
<span>Core {index}</span>
<span>{usage}%</span>
</div>
<Progress value={parseFloat(usage)} className="h-2" />
</div>
))}
</div>
<div className="space-y-2">
<h3 className="text-lg font-semibold text-foreground">Memory Usage</h3>
<div className="flex justify-between text-sm text-muted-foreground">
<span>Used</span>
<span>{systemInfo.memoryUsage.used.toFixed(2)} / {systemInfo.memoryUsage.total.toFixed(2)} GB</span>
</div>
<Progress
value={(systemInfo.memoryUsage.used / systemInfo.memoryUsage.total) * 100}
className="h-2"
/>
</div>
</CardContent>
</Card>
</main>
);
}
step 5:
src/lib/system.ts
import os from "os";
import { exec } from "child_process";
import { promisify } from "util";
const execAsync = promisify(exec);
function getCpuUsage() {
const cpus = os.cpus();
return cpus.map((cpu) => {
const total = Object.values(cpu.times).reduce((acc, tv) => acc + tv, 0);
const usage = 100 - (100 * cpu.times.idle) / total;
return usage.toFixed(1);
});
}
async function getCpuTemp() {
const { stdout } = await execAsync("vcgencmd measure_temp");
// in celsius! OBVIOUSLY!
return parseFloat(stdout.replace("temp=", "").replace("'C", ""));
}
function bytesToGB(bytes: number) {
return (bytes / (1024 * 1024 * 1024)).toFixed(2);
}
export async function getSystemDetails() {
// Get CPU usage
const cpuUsage = getCpuUsage();
// Get memory info
const totalMem = os.totalmem();
const freeMem = os.freemem();
const usedMem = totalMem - freeMem;
const cpuTemp = await getCpuTemp();
return {
os,
cpuTemp,
cpuUsage,
memoryUsage: {
total: parseFloat(bytesToGB(totalMem)),
used: parseFloat(bytesToGB(usedMem)),
free: parseFloat(bytesToGB(freeMem)),
},
};
}
src/lib/system.ts
import os from "os";
import { exec } from "child_process";
import { promisify } from "util";
const execAsync = promisify(exec);
function getCpuUsage() {
const cpus = os.cpus();
return cpus.map((cpu) => {
const total = Object.values(cpu.times).reduce((acc, tv) => acc + tv, 0);
const usage = 100 - (100 * cpu.times.idle) / total;
return usage.toFixed(1);
});
}
async function getCpuTemp() {
const { stdout } = await execAsync("vcgencmd measure_temp");
// in celsius! OBVIOUSLY!
return parseFloat(stdout.replace("temp=", "").replace("'C", ""));
}
function bytesToGB(bytes: number) {
return (bytes / (1024 * 1024 * 1024)).toFixed(2);
}
export async function getSystemDetails() {
// Get CPU usage
const cpuUsage = getCpuUsage();
// Get memory info
const totalMem = os.totalmem();
const freeMem = os.freemem();
const usedMem = totalMem - freeMem;
const cpuTemp = await getCpuTemp();
return {
os,
cpuTemp,
cpuUsage,
memoryUsage: {
total: parseFloat(bytesToGB(totalMem)),
used: parseFloat(bytesToGB(usedMem)),
free: parseFloat(bytesToGB(freeMem)),
},
};
}
src/lib/system.ts
import os from "os";
import { exec } from "child_process";
import { promisify } from "util";
const execAsync = promisify(exec);
function getCpuUsage() {
const cpus = os.cpus();
return cpus.map((cpu) => {
const total = Object.values(cpu.times).reduce((acc, tv) => acc + tv, 0);
const usage = 100 - (100 * cpu.times.idle) / total;
return usage.toFixed(1);
});
}
async function getCpuTemp() {
const { stdout } = await execAsync("vcgencmd measure_temp");
// in celsius! OBVIOUSLY!
return parseFloat(stdout.replace("temp=", "").replace("'C", ""));
}
function bytesToGB(bytes: number) {
return (bytes / (1024 * 1024 * 1024)).toFixed(2);
}
export async function getSystemDetails() {
// Get CPU usage
const cpuUsage = getCpuUsage();
// Get memory info
const totalMem = os.totalmem();
const freeMem = os.freemem();
const usedMem = totalMem - freeMem;
const cpuTemp = await getCpuTemp();
return {
os,
cpuTemp,
cpuUsage,
memoryUsage: {
total: parseFloat(bytesToGB(totalMem)),
used: parseFloat(bytesToGB(usedMem)),
free: parseFloat(bytesToGB(freeMem)),
},
};
}
src/lib/system.ts
import os from "os";
import { exec } from "child_process";
import { promisify } from "util";
const execAsync = promisify(exec);
function getCpuUsage() {
const cpus = os.cpus();
return cpus.map((cpu) => {
const total = Object.values(cpu.times).reduce((acc, tv) => acc + tv, 0);
const usage = 100 - (100 * cpu.times.idle) / total;
return usage.toFixed(1);
});
}
async function getCpuTemp() {
const { stdout } = await execAsync("vcgencmd measure_temp");
// in celsius! OBVIOUSLY!
return parseFloat(stdout.replace("temp=", "").replace("'C", ""));
}
function bytesToGB(bytes: number) {
return (bytes / (1024 * 1024 * 1024)).toFixed(2);
}
export async function getSystemDetails() {
// Get CPU usage
const cpuUsage = getCpuUsage();
// Get memory info
const totalMem = os.totalmem();
const freeMem = os.freemem();
const usedMem = totalMem - freeMem;
const cpuTemp = await getCpuTemp();
return {
os,
cpuTemp,
cpuUsage,
memoryUsage: {
total: parseFloat(bytesToGB(totalMem)),
used: parseFloat(bytesToGB(usedMem)),
free: parseFloat(bytesToGB(freeMem)),
},
};
}
Now if you navigate to the /stats
page, you should see a page with some basic system information about your Raspberry Pi.