Engineering notes

ENGINEERING NOTE 02

Inside the LAN: Next.js, PostgreSQL, and One Shared Learning State

The portal is a small two-service system with explicit server boundaries, durable shared state, and an operational trust assumption that must not be mistaken for authentication.

  • Next.js standaloneRuntime — A Node 22 container serves pages and route handlers from the framework's standalone output.
  • PostgreSQL 16Persistence — Curriculum releases, progress, sessions, responses, bookmarks, and audit events live in one durable database.
  • trusted LANIntended reach — The published web port is intended for local-network use and the document metadata is noindex/nofollow.

SECTION 01

Keep the topology deliberately small

A browser reaches one Next.js service; that service reaches PostgreSQL over the Compose network; only the web port is published to the host LAN.

The production image compiles a standalone Next.js application and starts it as an unprivileged Node process. Docker Compose waits for PostgreSQL health before starting the web service, injects the database connection only into the server container, and persists the database in a named volume. The browser never connects to PostgreSQL. Server components and route handlers query through the postgres client, while client components call same-origin JSON endpoints for mutations and interactive state.

That separation is more important than the number of containers. Curriculum pages can be server rendered from the published release, assessment handlers can create and grade sessions transactionally, and browser-only affordances such as narration or device preferences stay on the client. The topology has one state authority—the database—so a learner can continue the shared campaign from another device on the same LAN without reconciling browser caches.

Production request pathThe browser crosses the LAN to the web service; database traffic remains within the Compose network.A LAN browser communicates only with Next.js. Next.js performs server-side database work against PostgreSQL, whose data is retained in a Docker named volume. Connections: LAN browser to Next.js web (HTTP on the trusted LAN); Next.js web to PostgreSQL (parameterized SQL); PostgreSQL to Named volume (persistent storage).
  1. LAN browser

    Receives HTML and runs explicit client components.

  2. Next.js web

    Standalone Node server, pages, and route handlers.

  3. PostgreSQL

    Published curriculum and shared learner state.

  4. Named volume

    Durable database files across container replacement.

SECTION 02

Place each capability on the correct side of the boundary

Database credentials, grading keys, and release operations stay in server-only modules; pointer interactions, local preferences, and speech synthesis stay in client components.

The database and session-store modules import server-only, making an accidental client import fail during the build rather than leaking a connection path into a browser bundle. Route handlers validate JSON payloads, check same-origin mutation requests, and translate expected failures into public errors. Interactive components receive already-reduced data and never need the database connection string. This gives the codebase a reviewable boundary even though server and client code share one TypeScript project.

The browser still owns transient presentation state: which lesson section is active, whether narration is paused, which locally installed voice is selected, and which pathway card is expanded. Durable learning state—progress, answers, bookmarks, release IDs, and audit records—returns to PostgreSQL. Keeping those categories distinct prevents a cleared browser profile from erasing learning history and prevents convenience preferences from becoming shared-account policy.

State placement — Durability and sensitivity determine where state belongs.
StateOwnerReason
Published release and lesson progressPostgreSQLShared, durable, and transactionally updated.
Session item snapshots and grading keysServer + PostgreSQLMust not be trusted to the browser before feedback is due.
Narration voice and speedBrowser local storageDevice capability and personal preference vary by browser.
Pathway preview selectionBrowser local storageA reversible planning preference, not completion evidence.

SECTION 03

Model the shared learner honestly

The deployment uses one shared learner identifier, so the database coordinates progress across devices but does not provide individual identity or isolation.

Every progress and assessment query is scoped to a fixed shared learner row. That is appropriate for the stated household or single-candidate LAN workflow: opening the portal on a second device shows the same lesson percentages, bookmarks, practice history, and story XP. Monotonic lesson updates use a database greatest operation so an older 25% request cannot overwrite a concurrently saved 95% result.

The tradeoff must be explicit. Shared state is not multi-user tenancy, and it cannot attribute an answer or admin edit to a person. If the portal later serves unrelated learners, identity, authorization, row ownership, session invalidation, and privacy controls become product requirements rather than small schema additions. The current data model should be described as coordinated shared state, never as authenticated personalization.

SECTION 04

Treat LAN and noindex as context, not controls

The root metadata requests noindex/nofollow, but search directives and private addressing do not authenticate a request or close a network port.

The release advertises a private-network URL and binds the web process to all container interfaces so Docker can publish port 3000. Root metadata sets robots to noindex and nofollow, and sensitive result/admin pages reinforce that policy. Response headers add a restrictive content security policy, deny framing, disable selected device permissions, and require same-origin mutation requests. Together these measures reduce accidental exposure and browser attack surface.

They do not establish access control. A host firewall, segmented network, reverse proxy, TLS policy, or login system is outside this repository. Any device able to reach the published port can reach the learner and admin surfaces under the current model. Therefore the accurate claim is “designed for a trusted LAN and excluded from indexing,” not “secure because it is on a LAN.” Moving the service beyond that boundary requires an explicit security design before it requires marketing copy.

REFERENCES

Sources and further reading

  1. Server and Client ComponentsNext.js

    Framework reference for keeping data access on the server while isolating interactive browser code behind explicit client boundaries.

  2. Deploying a Next.js applicationNext.js

    Framework deployment reference for the standalone Node.js output used by the container image.

  3. TransactionsPostgreSQL Global Development Group

    Database reference for all-or-nothing release changes, session creation, grading, and auditable state transitions.