Skip to content
Talk to an engineer

Higher Education & Research

One codebase for the public site and the platform behind it

A public-facing site and an internal student-tracking platform, built years apart on stacks nobody still owned. We consolidated them into one application with one design system and one authorization model, and shipped it without a production regression.

Client
A university cybersecurity center and its program stafftxcr.tamu.edu (opens in a new tab)
Duration
20 months, four phases
Team
5 engineers, embedded customer
Year
2023–2025

The situation

What we walked into.

Two systems, built years apart, neither owned by anyone still on the team. They had drifted apart in the way that always happens when the same facts are maintained in two places, and routine content edits were arriving as interrupts to engineers rather than as work the program staff could do themselves.

The public site was failing at its actual job, which was explaining what the center did to the people it wanted to reach. The internal platform tracked students, projects and credentials, and a meaningful subset of that was personally identifiable student information.

The deployment target was university infrastructure with restricted egress, so every dependency had to be self-hostable and reproducible from CI rather than reachable as a managed service.

The team was five engineers working part-time around full course loads, with the center’s director and program staff reviewing weekly. That combination punishes any plan that needs a heroic month.

Approach

How we sequenced it.

Each step had to be independently valuable. That constraint is what let the client stop at any point without being stranded.

  1. Step 01

    Fix the scope before writing code

    Two requirements sessions produced a one-page scope document that fixed three things: the user roles, the data domains, and the in-scope use cases for each. Anything that did not map to one of those cells was explicitly out of scope and tracked in a parking lot. That document is what kept “just one more thing” from eating the schedule.

  2. Step 02

    Foundations before features

    The first phase shipped no user-visible functionality at all: repository, CI, design system, authentication skeleton and role primitives. Every later change landed on rails that already worked, which is the cheapest time to get them right and the only time anyone will agree to spend on it.

  3. Step 03

    Authorization at the server boundary, not the interface

    Three roles with explicit permissions on every mutation, enforced at the route handler rather than in the components. The interface hides what a user cannot do; the server refuses it regardless of what the interface shows. Anything touching authentication or personal data shipped integration tests against a real database in CI.

  4. Step 04

    Harden, then hand over

    A penetration test, a performance pass, audit logging and handover documentation, with each phase closing on an exit criterion the customer signed off. The rotation of who shipped the riskiest change each week meant no single engineer became the only person who understood a subsystem.

What was built

The parts that mattered.

  • One application serving a static public surface and a gated platform, sharing a design system and a data layer
  • Role-based access enforced at the route handler on every mutation, independent of interface state
  • Test-driven development as the default workflow, reaching 600+ unit and integration tests
  • Required-checks pipeline: typecheck, unit, integration and a preview deployment on every change
  • A data-exposure incident on a legacy route found, closed, credential-rotated and written up within 24 hours
  • Follow-on penetration test led in-house, with a remediation plan the center adopted

Results

What changed, and how we know.

3 of the 4 below are measurements, each against a stated baseline. The rest are states of the delivered system rather than numbers, and are written as such rather than dressed up as figures.

Production regressions
0Across the release window, with aggressive refactoring throughout
Tests at handover
600+The count is not the point; being able to refactor under deadline is
Incident to written post-mortem
24 hrsRoute pulled, credentials rotated, access logs audited, report delivered
Under staff who did not build it
Still runningThe handover held, which is the only real test of one
“Closed by default at the route layer is the only design that survives a moment of inattention.”
Engineering lead on the project, university cybersecurity center and its program staff

Built with

  • Next.js
  • TypeScript
  • PostgreSQL
  • Prisma
  • Tailwind
  • GitHub Actions

Next step

Tell us what’s breaking.

Forty-five minutes, no charge, no deck. We’ll tell you what we’d do, what it would likely cost, and whether what you already have can be made to work.

Reply
A person replies, not a sequence: within one business day, from someone who would be on the engagement.