@contensis/contensis-for-councils
v2.4.1-beta.2
Published
A single React codebase that powers multiple UK council websites from the [Contensis](https://www.contensis.com/) CMS. One application serves many tenant sites — each driven by its own Contensis project and per-site configuration, and each deployed as
Readme
Contensis for Councils
A single React codebase that powers multiple UK council websites from the Contensis CMS. One application serves many tenant sites — each driven by its own Contensis project and per-site configuration, and each deployed as its own Contensis block.
Built on @zengenti/contensis-react-base
(CRB) with server-side rendering, the Canvas
and Forms packages, and
full English / Welsh bilingual support (including Welsh-first sites that
serve Welsh at the site root).
🧩 Tech stack
- React 18 + TypeScript (strict)
@zengenti/contensis-react-basev4 — routing, search, SSR, Redux + sagas- Styled Components for styling (BEM class names for targeting)
@contensis/canvas-reactfor rich content,@contensis/formsfor forms- Webpack build via the
crbCLI; Express-backed SSR server - Jest for unit tests; ESLint + Prettier
- Docker + GitLab CI/CD for block deployment
Requires Node 18 and a modern npm (7+).
🔌 Getting started
- Create a
.envfile in the project root pointing at the CMS project you want to run (see Environment configuration below). - Install dependencies:
If you hit peer-dependency install errors, removenpm installnode_modulesand runnpm install --legacy-peer-deps. - Start the dev server (HMR + SSR):
npm run dev
🌎 Environment configuration
The app reads its CMS connection from a root .env file. Because this is a
multi-tenant project, the .env is what selects which council site you run
locally — point it at a given council's Contensis project and you're running
that site.
| Variable | Purpose |
| --- | --- |
| ALIAS | Contensis environment alias (resolves the CMS/Delivery API URLs) |
| PROJECT | Contensis project ID — the council site to serve |
| ACCESS_TOKEN | Delivery API access token for that project |
| PUBLIC_URL | The site's public URL (used for canonical URLs, sitemap, etc.) |
You can keep multiple environments side by side by adding a suffix —
e.g. .env.<name> — and activating it on start/build:
npm --env=<name> run dev📜 Scripts
| Script | Purpose |
| --- | --- |
| npm run dev | Development server with hot-reload and SSR |
| npm run build | Production build (client + server bundles) |
| npm run server | Run the built SSR server (dist/start.js) |
| npm run build:server | Build, then start the SSR server (for debugging server-side code) |
| npm run analyze | Production build with the webpack bundle analyzer |
| npm test | Run the Jest test suite |
| npm run test:watch | Jest in watch mode |
| npm run test:coverage | Jest with a coverage report |
| npm run lint / npm run lint:fix | ESLint check / auto-fix |
🗂️ Project structure
src/app/
├── App.tsx # Root app component
├── components/ # Reusable UI components (BEM + Styled Components)
├── templates/ # Page templates per content type (article, event,
│ # councillor, location, listings, search, home, …)
├── listings/ # Listing/search config shared across listing pages
├── routes/ # Static routes, content-type → template mappings, withEvents
├── locale/ # Bilingual engine: language resolution, routes, slugs
├── siteConfig/ # Per-site config (theme, nav, footer, alerts, search facets)
├── schema/ # Content types, fields, locales, images, logos
├── redux/ # Store wiring (reducers + sagas)
├── styles/ # Theme types and global styles
└── util/ # Mappers, hooks and helpersContent types are mapped to templates in src/app/routes/contentTypeMappings.ts
(article, event, councillor, location, post, the listing pages, etc.).
🌐 Localisation (English / Welsh)
The app is bilingual and supports a dynamic primary language — a Welsh-first
council serves Welsh at the site root (/, /chwilio) while English lives under
/en-gb, and an English-first council does the reverse. The primary language is
resolved at runtime from the Contensis project, not hard-coded.
- Locales are declared in
src/app/schema/locales.ts(path segments, aliases, per-locale route slugs such assearch↔chwilio). - Route registration, language resolution, canonical redirects and localised
search facets/filters live under
src/app/locale/. - The canonical URL form per locale is enforced with a 301 redirect (e.g.
/en/search→/en-gb/search).
🚀 Multi-tenancy & deployment
Each council site is a separate Contensis block deployed by GitLab CI. The
shared sandbox/demo environment is gov/website; client sites are defined one
file per council under .gitlab-ci/.
Releases are driven by the commit title on master (vX.Y.Z for a full release
to all sites, vX.Y.Z-beta for a beta to the demo and opted-in sites). Routine
master commits build but deploy nothing.
See docs/ci-cd.md for the full pipeline: branches,
releases, the image-tagging model, onboarding a new council, and the per-job
reference.
🍃 Contensis Delivery API
When writing backing code that calls the Delivery API, prefer the helpers
exported from @zengenti/contensis-react-base/util (see
src/app/util/contensisDeliveryClient.ts).
📝 Conventions
- Commits follow Conventional Commits
— e.g.
feat: header component,fix(search): …. - Styling uses Styled Components; element class names follow the BEM (Block, Element, Modifier) convention.
- TypeScript runs in strict mode; add explicit types and CMS-data mappers.
📚 Further docs
docs/ci-cd.md— CI/CD and block deploymentdocs/security-exceptions.md— acceptednpm auditexceptionsdocs/cookie-audit.md— cookie auditdocs/plans/— design/implementation plans- Releasing to live (Notion)
