Skip to main content

NDX Website

Last Updated: 2026-03-06 Source: https://github.com/co-cddo/ndx Captured SHA: b846188

Executive Summary

The National Digital Exchange (NDX) website is a public-facing static site built with Eleventy (11ty) v3.x and hosted on AWS using CloudFront and S3. It serves as the primary informational platform for the NDX initiative, providing a service catalogue with "Try Before You Buy" integration, a self-service signup system for UK public sector employees, and educational content on UK local government digital transformation. Infrastructure is managed through AWS CDK across two accounts (NDX and ISB), with a cookie-based CloudFront routing strategy that allows the NDX static site to coexist on the same distribution as the Innovation Sandbox UI.

Architecture Overview

Eleventy Static Site Configuration

File: repos/ndx/eleventy.config.js (353 lines)

The Eleventy configuration implements a multi-bundle build pipeline with GOV.UK Design System integration.

GOV.UK Plugin Configuration

The site uses @x-govuk/govuk-eleventy-plugin v8.3.1 with the following navigation structure:

Navigation ItemPath
Home/
About/About/
Catalogue/catalogue/tags/try-before-you-buy/
Try/try
Optimise/optimise/

The site is branded as "National Digital Exchange" in Alpha phase, with a user research link in the phase banner pointing to ConsentKit.

TypeScript Bundling with esbuild

Two separate JavaScript bundles are produced via esbuild:

BundleEntry PointOutputPurpose
Trysrc/try/main.tssrc/assets/try.bundle.jsTry Before You Buy interactions
Signupsrc/signup/main.tssrc/assets/signup.bundle.jsSignup form functionality

Both bundles target ES2020 in ESM format. In development mode, esbuild uses context-based watch mode with sourcemaps; in production, bundles are minified without sourcemaps.

Eleventy Collections

CollectionSource GlobSortPurpose
cataloguesrc/catalogue/**/*.mdAlphabetical by titleService catalogue items
catalogueByTagSame as catalogueGrouped by tag nameTag-indexed services
catalogueTryableSame, filtered try: trueAlphabeticalTry-enabled services only
challengessrc/challenges/**/*.mdDate descendingInnovation challenges
reviewssrc/reviews/**/*.mdDate descendingUser reviews
newssrc/discover/news/**/*.mdDefaultNews articles
eventsrc/discover/events/**/*.mdDefaultEvents
casestudysrc/discover/case-studies/**/*.mdDefaultCase studies
productAssessmentssrc/product-assessments/**/*.mdDate descendingProduct reviews

Content Validation

UUID validation is enforced at build time for Try metadata on catalogue items. The build warns if try_id is not a valid UUID or if a try_id is present without try: true.

Plugins

  • Mermaid Transform (lib/eleventy-mermaid-transform.js): Converts Mermaid code blocks to SVG at build time, eliminating client-side rendering.
  • Remote Images (lib/eleventy-remote-images.js): Fetches images from img.shields.io and cdn.jsdelivr.net at build time to reduce external dependencies.
  • Remote Include: Shortcode that fetches content from GitHub via cdn.jsdelivr.net, with relative image URL rewriting.

Site Structure

src/
├── About/ # About NDX pages
├── _includes/ # Nunjucks templates
├── acceptable-use-policy.md # AUP
├── accessibility.md # WCAG 2.2 AA statement
├── assets/ # Static assets (CSS, JS bundles, images)
├── begin/ # Getting started guides
├── catalogue/ # Service catalogue (aws/, anthropic/, cloudflare/, govuk/, microsoft/)
│ └── tags/ # Tag landing pages
├── challenges/ # Innovation challenges
├── cookies.md # Cookie policy
├── index.md # Homepage
├── optimise/ # Optimisation resources
├── privacy.md # Privacy policy
├── product-assessments/ # Product reviews
├── reviews/ # User reviews
├── robots.txt # Search engine directives
├── sass/ # SCSS source files
├── signup/ # Signup flow pages (8 files)
├── signup.md # Signup landing page
├── try/ # Try Before You Buy functionality (12 files)
└── todo.md # Internal tracker

Catalogue Item Frontmatter Schema

---
title: Service Name
description: Brief description
image: /assets/catalogue/vendor/logo.svg
vendor: Vendor Name
tags: [AI, try-before-you-buy]
try: true # Enable Try integration
try_id: 550e8400-e29b-41d4-a716-446655440000 # Valid UUID required
deployment_time: "15 minutes"
estimated_cost: "Free"
---

Infrastructure: CDK Stacks

The NDX infrastructure is split across four CDK stacks plus one CloudFormation template, deployed to the NDX account (568672915267) with one cross-account deployment to the ISB account (955063685555).

NdxStaticStack (infra/lib/ndx-stack.ts)

Provisions the S3 bucket and integrates with the existing CloudFront distribution.

S3 Bucket (ndx-static-prod):

  • SSE-S3 encryption, all public access blocked
  • Versioning enabled for rollback, RETAIN removal policy
  • Bucket policy restricts s3:GetObject to CloudFront service principal scoped to distribution ARN

CloudFront Integration:

  • Imports existing distribution E3THG4UHYDHVWP (not CDK-managed)
  • Custom Resource Lambda (AddCloudFrontOriginFunction, Node.js 20) adds origins and cache behaviors via CloudFront API
  • Two origins added: S3 origin with OAC, and Lambda Function URL origin with SigV4 OAC

Cookie Router Function (ndx-cookie-router, CloudFront JS 2.0):

  • If NDX=legacy cookie present: routes to legacy ISB SPA origin, rewrites all non-file URIs to /index.html
  • Otherwise: routes to ndx-static-prod S3 origin with OAC, rewrites directory-style URLs to index.html
  • Safety routing for /signup-api/* and /api/* paths to their dedicated origins

Cache Policy (NdxCookieRoutingPolicy):

  • Default TTL: 1 day, Max: 1 year
  • Forwards only the NDX cookie
  • All query strings forwarded
  • Gzip and Brotli compression enabled

Response Headers Policy (NdxSecurityHeadersPolicy):

  • Content Security Policy allowing Google Analytics 4 Measurement Protocol
  • X-Frame-Options: DENY, X-Content-Type-Options: nosniff
  • Referrer-Policy: no-referrer
  • HSTS: 540 days with includeSubdomains

Signup Lambda Cache Behavior (/signup-api/*):

  • Origin: Lambda Function URL with SigV4 OAC
  • Cache policy: AWS managed CachingDisabled
  • Origin request policy: AllViewerExceptHostHeader

WafStack (infra/lib/waf-stack.ts)

Deployed to us-east-1 (required for CloudFront WAF).

  • Rate-based rule: 10 requests per 5-minute window per IP, scoped to /signup-api/signup
  • Custom 429 response body with JSON error message
  • CloudWatch Logs (aws-waf-logs-ndx-signup, 90-day retention)
  • Sampled requests enabled for forensics

NdxNotificationStack (infra/lib/notification-stack.ts)

Event-driven notification system processing ISB lease lifecycle events.

Components:

  • Notification Lambda (Node.js 20, 512MB, 30s timeout) with X-Ray tracing
  • EventBridge rules subscribing to ISB event bus for lease events
  • GOV.UK Notify integration for user emails (Secrets Manager for API key)
  • AWS Chatbot integration for Slack ops alerts (EventBridge to SNS to Chatbot)
  • SQS Dead Letter Queue (14-day retention) with daily digest Lambda
  • CloudWatch dashboard with alarm thresholds for DLQ depth, Lambda errors, secret rotation, deliverability rates

Notification Lambda Modules (33 files in infra/lib/lambda/notification/):

  • handler.ts: Main event processor
  • enrichment.ts: Event data enrichment from ISB API
  • templates.ts: GOV.UK Notify email template management
  • validation.ts: Event schema validation
  • idempotency.ts: DynamoDB-based deduplication
  • ownership.ts: Lease ownership tracking
  • notify-sender.ts: GOV.UK Notify API integration
  • secrets.ts: Secrets Manager client with rotation tracking
  • dlq-digest-handler.ts: Daily DLQ summary for Slack

SignupStack (infra-signup/lib/signup-stack.ts)

See 31-signup-flow.md for detailed analysis.

GitHub Actions Stack (infra/lib/github-actions-stack.ts)

Provisions OIDC roles for GitHub Actions CI/CD pipelines.

Deployment Pipeline

CI Workflow (ci.yaml)

Triggers on push to main, pull requests, and merge queue.

Path-based optimization: Frontend changes (files outside infra/ and docs/) trigger build/test/deploy. Infrastructure-only changes skip frontend jobs.

JobDescriptionParallelism
buildEleventy build, lint (Prettier), artifact uploadSingle
test-unitJest unit testsSingle
test-e2ePlaywright E2E tests2 shards
test-a11yPlaywright accessibility tests2 shards
deploy-s3S3 sync + CloudFront invalidationOnly on main merge
semverSemantic version generationSingle

Deployment (main branch only):

  1. Download build artifact
  2. OIDC authentication via GitHubActions-NDX-ContentDeploy role
  3. aws s3 sync to ndx-static-prod with max-age=3600
  4. Smoke test: validate index.html exists
  5. CloudFront invalidation (/*)

Infrastructure Workflow (infra.yaml)

Triggers on push to main, pull requests, and merge queue.

JobTriggerAccountDescription
infra-unit-testsinfra/** changesN/AJest unit tests for CDK stacks
cdk-diffPR only (non-fork)NDX (readonly)CDK diff posted as PR comment
cdk-deployMain mergeNDX (568672915267)Deploy all CDK stacks
signup-infra-unit-testsinfra-signup/** changesN/AJest tests for signup stack
signup-cdk-deployMain mergeNDX (568672915267)Deploy signup Lambda
isb-cross-account-role-deployMain mergeISB (955063685555)Deploy cross-account IAM role

Security measures:

  • OIDC authentication (no long-lived credentials)
  • Fork PRs blocked from assuming AWS roles (both at workflow and IAM level)
  • Separate readonly (InfraDiff) and deploy (InfraDeploy) roles
  • step-security/harden-runner on all jobs

Deploy Script (scripts/deploy.sh)

Manual deployment script for local use:

  1. Validates _site/ directory exists
  2. aws s3 sync with NDX/InnovationSandboxHub profile
  3. File count validation between local and remote
  4. Smoke tests: index.html, CSS, JS files
  5. CloudFront invalidation for distribution E3THG4UHYDHVWP

Testing Strategy

LayerToolCoverage
Unit TestsJest 30 + ts-jestTypeScript modules, lib plugins
E2E TestsPlaywright 1.58Auth, catalogue, signup, smoke, try flows
AccessibilityPlaywright + axe-coreWCAG 2.2 AA compliance per page
LintPrettier 3.8Code formatting (120 char width, no semicolons)
InfrastructureJestCDK snapshot and assertion tests

Local E2E setup requires mitmproxy (scripts/mitmproxy-addon.py) running on port 8081 to intercept and mock ISB API calls during development.

Environment Variables

VariableDescriptionDefault
AWS_SSO_PORTAL_URLAWS SSO portal for console accesshttps://d-9267e1e371.awsapps.com/start
API_BASE_URLInnovation Sandbox API base URL/api
REQUEST_TIMEOUTAPI request timeout (ms)10000
PATH_PREFIXEleventy path prefix/

Security Posture

  • TLS: 1.2+ enforced on CloudFront, HSTS 540 days
  • CSP: Restrictive policy with default-src 'none', only self and Google Analytics allowed
  • WAF: Rate limiting on signup API (10 req/5min per IP)
  • S3: All public access blocked, OAC-only via CloudFront
  • Lambda: IAM auth on Function URL, SigV4 signing via OAC
  • OIDC: GitHub Actions uses short-lived tokens, no stored credentials
  • Fork Protection: CDK diff/deploy blocked for fork PRs at workflow and IAM level

Source Files Referenced

File PathPurposeLines
repos/ndx/eleventy.config.jsEleventy build configuration353
repos/ndx/package.jsonDependencies and scripts74
repos/ndx/infra/lib/ndx-stack.tsMain CDK stack374
repos/ndx/infra/lib/notification-stack.tsNotification infrastructure1000+
repos/ndx/infra/lib/waf-stack.tsWAF rate limiting182
repos/ndx/infra/lib/config.tsEnvironment configuration~200
repos/ndx/infra/lib/functions/cookie-router.jsCloudFront Function66
repos/ndx/infra/lib/functions/add-cloudfront-origin.tsCustom Resource Lambda500+
repos/ndx/infra-signup/lib/signup-stack.tsSignup Lambda stack371
repos/ndx/scripts/deploy.shManual deploy script74
repos/ndx/.github/workflows/ci.yamlCI pipeline331
repos/ndx/.github/workflows/infra.yamlInfrastructure pipeline431

Generated from source analysis. See 00-repo-inventory.md for full inventory.