🌐 Build a Web AI App

Next.js + NVIDIA Nemotron (Build API)

SJSU Β· Edge AI

A streaming chatbot web app that runs on your Jetson and serves to any browser.

1 What you'll build

  • A Next.js web app with a streaming chat UI.
  • Talks to NVIDIA Nemotron (and optionally OpenAI / Anthropic) via cloud APIs.
  • Runs on the Jetson, opened from your laptop's browser.
  • πŸ”’ Your API key stays server‑side β€” never sent to the browser.

2 The big picture

Your browser talks to a small server on the Jetson, which talks to the model API:

Browser β†’ /api/chat (on Jetson) β†’ NVIDIA/OpenAI/Anthropic β†’ stream back

  • The page runs in the browser (buttons, live text).
  • The API route runs on the server β€” it holds the key and calls the model.
  • The browser never sees the key.

3 Key web concepts (background)

  • Next.js = a React framework. Each folder under app/ is a page (the App Router).
  • Server Components / routes run on the Jetson β€” they can hold secrets and call APIs.
  • Client Components run in the browser β€” they're interactive ("use client").
  • Streaming (SSE): the server forwards tokens one chunk at a time, so the answer appears live.

Rule of thumb: secrets + external API calls β†’ server; buttons + live updates β†’ client.

4 Where's the code (and what each part does)

edgeLLM/nextjs-nemotron-app/
  app/page.js               # the chat UI  β€” Client Component (browser)
  app/components/ChatUI.js  # streaming chat box
  app/api/chat/route.js     # server route β€” holds the key, streams from the model
  app/api/models/route.js   # the model dropdown list
  lib/providers.js          # picks NVIDIA/OpenAI/Anthropic + reads keys from ~/.env.local
  .env.local                # optional local keys (git-ignored)

πŸ“¦ Repo: edgeLLM/nextjs-nemotron-app

5 Setup β€” API keys

Keys come from your ~/.env.local (the same file sjsujetsontool chat saved). Add any of:

echo "NVIDIA_API_KEY=nvapi-…"     >> ~/.env.local   # build.nvidia.com (free)
echo "OPENAI_API_KEY=sk-…"        >> ~/.env.local   # platform.openai.com
echo "ANTHROPIC_API_KEY=sk-ant-…" >> ~/.env.local   # console.anthropic.com

The app picks the provider from the model you choose (nvidia/…, gpt-…, claude-…).

πŸ› οΈ No Node on the host or in the container yet? That's normal β€” the next slide installs
everything with one command. No sudo is needed anywhere.

6 Run the frontend β€” sjsujetsontool node

One command installs Node in the container, runs npm install, and starts the dev server.
Run from anywhere on the host (your home is fine):

sjsujetsontool node             # interactive: prompts for path + mode

It asks the path with a sensible default β€” press Enter for this lesson:

🟒 node v20.20.2 · npm 10.8.2  (inside container jetson-dev)
πŸ“ Project path? [Enter = /Developer/edgeAI/edgeLLM/nextjs-nemotron-app]:
πŸ“¦ Project: /Developer/edgeAI/edgeLLM/nextjs-nemotron-app
β–Ά  Start the frontend now? [f]oreground / [b]ackground / [n]o:  b
πŸš€ Starting in BACKGROUND on port 3000.   β€’ URL: http://192.168.5.206:3000

Shortcuts (skip the prompts):

sjsujetsontool node bg                          # bg, default path
sjsujetsontool node fg /Developer/my-vite-app   # fg, explicit path (any order)
sjsujetsontool node /Developer/my-app bg        # path + mode, swapped
sjsujetsontool node stop                        # stop a background server
sjsujetsontool node clean                       # wipe .next cache β€” fixes "Module not found"
sjsujetsontool node clean all                   # also wipe node_modules (forces a fresh npm install)

Stuck on Module not found? Run sjsujetsontool node clean then sjsujetsontool node bg.
Routes through the container so the student account doesn't need sudo to delete the root-owned
.next folder. sjsujetsontool update now also wipes stale caches in Step 5/5 automatically.

7 Manual install (what sjsujetsontool node does for you)

If you ever need to install Node by hand β€” or you want to see what the one-step command
runs β€” open a container shell and use NodeSource's apt repo (Ubuntu 24.04 aarch64, root inside,
no sudo):

sjsujetsontool shell                                # drops into root@jetson-dev:/workspace
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
apt-get install -y nodejs                           # β†’ node v20.20.2 Β· npm 10.8.2

Then build and run the app the classic way:

cd /Developer/edgeAI/edgeLLM/nextjs-nemotron-app
npm install                                         # first time only (~30-60 s)
npm run dev                                         # serves on 0.0.0.0:3000

The Node install lives in the container's writable layer β€” it persists across
sjsujetsontool shell invocations until the image is rebuilt. node_modules/ lives
on the host SSD because the parent dir is a host mount.

8 Open it from your laptop β€” on the same LAN

Find the Jetson's IP and open it in any browser:

hostname -I | awk '{print $1}'        # e.g. 192.168.5.206  β†’ http://192.168.5.206:3000

Each message streams Jetson β†’ model API β†’ Jetson β†’ your browser, with a live TTFT and
tokens-per-second line under the chat. To stop a backgrounded server:

sjsujetsontool node stop

9 Open it from your laptop β€” over SSH (off-LAN)

Working from home / a hotel / a Headscale tunnel? You don't need Tailscale on your laptop β€”
SSH itself can forward the port:

# On your laptop, in a NEW terminal β€” keep it open while you use the app:
ssh -p 20065 \
    -L 3000:localhost:3000 \      # Next.js dev server
    -L 8002:localhost:8002 \      # Agent Lab sidecar (optional)
    student@headscale.forgengi.org -N

Then open <http://localhost:3000> in your laptop browser. Traffic rides the encrypted
SSH tunnel; nothing is exposed publicly.

Free bonus: Browsers treat http://localhost as a secure context, so the mic in
the ASR/Omni labs works through the tunnel without HTTPS. Direct LAN IPs don't get that.

**Common snags** β€” `bind: Address already in use` β†’ use a different left side (`-L 13000:localhost:3000` β†’ open `localhost:13000`). Tunnel dies after a few minutes β†’ add `-o ServerAliveInterval=30`.

10 Extend it β€” same pattern every time

Every feature = one page (UI) + one API route (server logic). Copy the pattern:

app/<feature>/page.js        # the page/UI
app/api/<feature>/route.js   # server: read key, call a model, return/stream

The bonus labs are exactly this β€” add a route + page and you've extended the app:

πŸ”Ž Retrieval Β· πŸ–ΌοΈ Omni (vision) Β· πŸŽ™οΈ ASR Β· πŸ”Š TTS Β· πŸ› οΈ Agent Lab (files + web)

Agent Lab backend menu mirrors sjsujetsontool chat:
🟒 NVIDIA Build Β· πŸ¦™ Local llama.cpp (:8080) Β· πŸŽ“ Shared SJSU node05 (no key needed) Β·
πŸ€– OpenAI Β· ✨ Anthropic Β· βš™οΈ Custom. Switch with one dropdown β€” see Lesson 11b.

11 Make it your own (push to GitHub)

Copy the app into your own folder, then create your repo:

cp -r /Developer/edgeAI/edgeLLM/nextjs-nemotron-app ~/my-ai-app
cd ~/my-ai-app && rm -rf node_modules .next
git init && git add -A && git commit -m "My Edge AI web app"

Create an empty repo on github.com (the οΌ‹ β†’ New repository), then:

git remote add origin https://github.com/<your-username>/my-ai-app.git
git branch -M main && git push -u origin main

With the GitHub CLI it's one line: gh repo create my-ai-app --public --source=. --push

πŸŽ₯ Video Demo: Next.js App in Action

See the Next.js chat interface stream tokens and execute tasks via the FastAPI agent backend.

Streaming chat tokens and executing coding agent tasks locally on the Jetson

πŸ› οΈ Agent Lab β€” a separate module

The chat lab finished. Next: turn the same app into a multi-round agent that can
read Β· grep Β· search Β· write Β· edit files (and optionally web-search) on the Jetson.
One more sjsujetsontool command brings the backend up.

12 Run the Agent Lab backend β€” sjsujetsontool agent

The Agent Lab (/agent page) needs a second server next to Next.js: a small FastAPI
process that hosts the edge_agent ReAct loop + the file-tool kit
(read_file / grep / search_files / write_file / edit_file + optional web_search).
One more sjsujetsontool command does the whole setup β€” no sudo:

sjsujetsontool agent bg          # install fastapi+uvicorn+edge_agent in ~/.venv, run on :8002
sjsujetsontool agent status      # β†’ 🟒 up on :8002, lists tools + workspace
sjsujetsontool agent stop

After that you have two backgrounded processes sharing ~/.env.local:

Next.js  on :3000  ← sjsujetsontool node  bg     # browser-facing UI         (in container)
FastAPI  on :8002  ← sjsujetsontool agent bg     # ReAct loop + tool kit     (on the host)
                            β–²
                       reads ~/.env.local
                       NVIDIA_API_KEY, SERPAPI_API_KEY, …

Why one in the container, one on the host? Node lives where apt can install it (the
container). The FastAPI backend needs the same ~/.venv your sjsujetsontool chat command uses,
so it stays on the host. --network host lets them reach each other at localhost.

πŸŽ₯ Video Demo: Agent Lab in Action

Observe how the Next.js Agent Lab runs reasoning-action loops (ReAct) with file tools on the Jetson.

Key Agent Options

  • Brain Options: Switch between NVIDIA API, local llama.cpp on Orin, or shared server (node05).
  • Policy Control: Change system instructions to require planning first, or enforce a read-only code auditor.
  • Full Architecture: For details on the ReAct control loop, tools, and python implementation, see the ReAct Agents slides.
ReAct agent solving a coding task live

πŸ“š Full walkthrough

Step‑by‑step build, code, and the four bonus labs:

lkk688.github.io/edgeAI/curriculum/11_nextjs_nemotron_app

← Back to the Get‑Started slides