FamilyNexus Site Guide

Everything you need to know about how this landing page is built, configured and deployed.

Architecture

The landing page is a pure static HTML site hosted on Azure Static Web Apps (Free tier). There is no build step, no framework, no Node runtime — just index.html, staticwebapp.config.json, and optional assets.

Azure Static Web App
Name: familynexus-landing · Resource group: rg-familynexus · Region: West Europe
Default URL: black-plant-03dabae03.7.azurestaticapps.net
Custom domains: familynexus.co.uk · www.familynexus.co.uk

The two apps (Budget and Schedule) are separate Azure App Services running Node.js, each with their own custom subdomain:

AppURLAzure resource
Pocket Budgetbudget.familynexus.co.ukpocket-budget-v3-sutton
Pocket Schedulescheduler.familynexus.co.ukpocket-schedule-v2-sutton

File structure

landing/
├── index.html              ← Landing page (edit this to change content)
├── guide.html              ← This page
├── admin.html              ← Settings admin UI
├── settings.json           ← Site settings (announcement, etc.)
├── favicon.svg             ← Site icon (FN gradient logo)
└── staticwebapp.config.json ← Azure routing + security headers

.github/
└── workflows/
    └── deploy-landing.yml  ← GitHub Actions deploy workflow

To update the site content, edit index.html and either push to GitHub (auto-deploys) or use the SWA CLI directly.

staticwebapp.config.json

This file controls how Azure Static Web Apps handles routing, headers and error pages. It sits in the landing/ folder alongside index.html.

{
  "navigationFallback": {
    "rewrite": "/index.html",
    "exclude": ["*.{css,js,png,gif,ico,jpg,svg,webp,woff,woff2,ttf}"]
  },
  "globalHeaders": { ... security headers ... },
  "routes": [],
  "responseOverrides": {
    "404": { "rewrite": "/index.html", "statusCode": 200 }
  }
}

Security headers

All applied via globalHeaders so they cover every response:

HeaderPurpose
Strict-Transport-SecurityForces HTTPS for 2 years, including subdomains. Prevents protocol downgrade attacks.
X-Content-Type-Options: nosniffStops browsers guessing MIME types. Prevents content-type sniffing attacks.
X-Frame-Options: DENYBlocks the page being embedded in an iframe. Prevents clickjacking.
X-XSS-ProtectionLegacy XSS filter for older browsers.
Referrer-PolicyOnly sends referrer to same origin. Reduces information leakage to third parties.
Permissions-PolicyDisables camera, microphone, geolocation and interest-cohort (FLoC) APIs.
Content-Security-PolicyRestricts what resources the page can load. Inline styles allowed; external connect blocked.
cache-control5-minute cache, 1-hour stale-while-revalidate. Balances freshness vs CDN performance.

Routing notes

The routes array is intentionally empty. Previously a /* wildcard route was here for cache headers, but it caused a validation conflict with the navigationFallback.exclude patterns (Azure treats exclude entries as implied routes). Cache headers are now in globalHeaders instead.

Important: if you add routes, put more specific routes before any wildcard (/*) otherwise they are unreachable and deployment fails.

settings.json

A lightweight JSON file the landing page fetches on load. Used to control the announcement banner without redeploying the HTML. Edit via the Admin UI or directly in the file.

{
  "announcement": "",
  "announcementStyle": "info"
}
FieldValuesEffect
announcementAny string, or emptyIf non-empty, shows a banner bar at the top of the page
announcementStyleinfo · success · warningControls banner colour (blue · green · amber)

Deploying with SWA CLI

The fastest way to push changes without going through GitHub Actions.

# One-off deploy (uses the deployment token)
npx @azure/static-web-apps-cli deploy ./landing \
  --deployment-token "$(az staticwebapp secrets list \
      --name familynexus-landing \
      --resource-group rg-familynexus \
      --query 'properties.apiKey' -o tsv)" \
  --env production

To get the token separately and reuse it:

az staticwebapp secrets list \
  --name familynexus-landing \
  --resource-group rg-familynexus \
  --query "properties.apiKey" -o tsv

The token is long-lived but can be rotated in the Azure portal under Static Web Apps → familynexus-landing → Manage deployment token. If you rotate it you must update the GitHub secret too.

GitHub Actions auto-deploy

Any push to main that touches a file inside landing/ triggers the deploy workflow automatically.

# Workflow file
.github/workflows/deploy-landing.yml

# Triggers on:
push → main → paths: landing/**
workflow_dispatch (manual trigger from Actions tab)

GitHub secret

The workflow authenticates using a secret stored in the repo:

Secret nameValue
AZURE_STATIC_WEB_APPS_TOKENDeployment token from Azure (see above)

To update it: gh secret set AZURE_STATIC_WEB_APPS_TOKEN --body "TOKEN" --repo AS-199-FamNexus/PocketBudget

Common failure causes

  • "No matching static web app" — token is stale or wrong. Fetch a fresh one from Azure and update the secret.
  • "Route covered by wildcard" — a specific route in staticwebapp.config.json appears after a /* wildcard. Delete or move it before the wildcard.
  • Node.js deprecation warning — set FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true in workflow env (already done).

DNS records (GoDaddy)

All DNS is managed at GoDaddy for familynexus.co.uk. Records needed:

TypeNamePoints toPurpose
ALIAS/CNAME@black-plant-03dabae03.7.azurestaticapps.netApex → landing page
CNAMEwwwblack-plant-03dabae03.7.azurestaticapps.netwww → landing page
CNAMEbudgetpocket-budget-v3-sutton.azurewebsites.netbudget subdomain → Budget app
CNAMEschedulerpocket-schedule-v2-sutton.azurewebsites.netscheduler subdomain → Schedule app
TXTasuid.budget(Azure verification ID)Proves domain ownership for Budget
TXTasuid.scheduler(Azure verification ID)Proves domain ownership for Schedule

To get verification IDs: az webapp show --name pocket-budget-v3-sutton --resource-group rg-familynexus --query customDomainVerificationId -o tsv

SSL certificates

All certificates are free Azure-managed certificates — they auto-renew and require no action.

  • familynexus.co.uk and www.familynexus.co.uk — managed by Azure Static Web Apps automatically when custom domains are bound.
  • budget.familynexus.co.uk and scheduler.familynexus.co.uk — managed certificates via App Service, provisioned by Terraform after DNS propagates.

If a certificate shows as pending, check the DNS records are correct and wait up to 15 minutes for Azure to verify ownership.

Pocket Budget

Runtime info
Azure App Service: pocket-budget-v3-sutton
URL: budget.familynexus.co.uk
Admin: budget.familynexus.co.uk/admin
Data: JSON files in DATA_DIR on the App Service filesystem
Pricing: configurable in Admin → Settings (served via /api/config publicly)

The budget app's admin panel controls pricing, maintenance mode, registration, Stripe keys and announcements. Changes take effect immediately without redeploying.

Pocket Schedule

Runtime info
Azure App Service: pocket-schedule-v2-sutton
URL: scheduler.familynexus.co.uk
Admin: scheduler.familynexus.co.uk/admin
Data: JSON files in DATA_DIR on the App Service filesystem
Pricing: configurable in Admin → Settings (served via /api/config publicly)