Uos.is



Overview
Uos.is is the public website for Orkustofnun, the National Energy Authority of Iceland. It is a bilingual (Icelandic default, English) content site that publishes news, events, public disclosures, regulations, and an open data catalogue of boreholes, maps, drawings, and licenses. The site is delivered from Prismic as a headless CMS and rendered by a Next.js App Router frontend.
The project is heavily integrated with third-party services. Algolia powers full-text search across content documents, SendGrid drives two email systems (a newsletter and a public-disclosure email-alert subscription flow with double opt-in confirmation), and Mapbox/MapLibre renders the map-based data pages. Borehole detail pages embed Plotly charts generated from measurement data.
Internationalization is a first-class concern. The site defaults to Icelandic routes (/frettir, /vidburdir, /opinber-birting) with parallel English routes (/en/news, /en/event, /en/public-disclosure), and a custom middleware layer resolves CMS-defined redirects for both locales.
Tech Stack & Rationale
Layer | Technology | Why |
|---|---|---|
Framework | Next.js 15 (App Router), React 19 | Server Components, per-locale route groups, and file-based API routes in one framework |
Language | TypeScript (strict) | Typed Prismic document models (prismicio-types.d.ts) and API payloads |
Styling | Tailwind CSS 3 | Utility-first styling with a theme-variable system for per-layout color schemes |
CMS | Prismic + Slice Machine | Headless content with 14 custom types and ~39 reusable slices, auto previews and cache-tag revalidation |
Search | Algolia (v5) | Instant full-text search with date-sorted results and type/date-enriched suggestions |
SendGrid (@sendgrid/mail) | Newsletter and email-alert subscriptions with confirm/unsubscribe API flows | |
Data viz / maps | Mapbox GL + react-map-gl, Plotly.js | Interactive maps for data pages and chart rendering for borehole measurements |
Client data | SWR 2 | Revalidation for search and feature data on the client |
UI helpers | Headless UI, Embla Carousel, react-select | Accessible modals/menus, carousels, and searchable selects |
SEO | schema-dts | Typed JSON-LD structured data generation |
Architecture
A request is first inspected by middleware.ts, which resolves CMS-defined redirects, then routed into a per-locale route group where a server component fetches Prismic content and renders the matching slice. Mutations and integrations happen through dedicated API routes.
The flow runs: HTTP request → middleware.ts (redirects · locale) → [locale] route group (is · en) → Server component (Prismic fetch); HTTP request → middleware.ts (redirects · locale) → [locale] route group (is · en) → SliceZone render (39 slices); app/api routes (email · search · feeds).
- Middleware / redirects — middleware.ts reads a custom_redirects document from Prismic (cached for 5 minutes), extracts the locale from the path (is or en), and issues locale-aware 301/302 redirects. Content slugs are mapped to localized routes (e.g. /frettir/:uid → /en/news/:uid).
- Routing — the app uses the (routes)/[locale]/(prismic) route groups, with (is), (en), and (default) sub-groups. The default group holds the data catalogue pages (boreholes, drawings, licenses, maps), while (is) and (en) hold the localized news, event, and public-disclosure routes, plus a dedicated search route.
- Content fetch — server components call createClient() from src/config/prismic.ts, which registers a route resolver (news, public_disclosure, event per locale) and caches Prismic requests with the prismic/redirects tags in production. On content publish, the /api/revalidate route purges those tags.
- Slice rendering — page content is composed through a SliceZone of the ~39 slices (Hero, News, PublicDisclosure, StatisticOverview, AirQuality, Borehole charts, etc.), with a layoutUid context that themes slices per layout (e.g. the Roe theme).
- Integrations — dedicated API routes talk to third parties: /api/algolia/sync-pages pushes content to Algolia, /api/email-alert/* handles SendGrid subscriptions, and /api/integrations/icelandic-weather and /api/integrations/legislations proxy the Vedur weather feed and Icelandic legislation data.
Folder Structure
uos/
├── app/ # Next.js App Router (route groups + API routes)
│ ├── (routes)/[locale]/ # localized pages: (is), (en), (default) data catalogue
│ ├── (email-alert)/ # email-alert success/error confirmation pages
│ └── api/ # algolia, email-alert, events, feedback, integrations, preview, revalidate
├── customtypes/ # Prismic models (page, news, event, layout, employee, menus, …)
├── slices/ # ~39 reusable Prismic slices
├── src/
│ ├── components/ # feature + UI components (news, events, boreholes, search, email-alerts)
│ ├── config/prismic.ts # Prismic client + route resolver
│ ├── data/ # local JSON (email subscriptions) + logs
│ ├── features/ # boreholes, events feature modules
│ └── shared/ # constants, i18n, hooks, lib, types, utils
├── middleware.ts # locale-aware redirect resolution from Prismic
├── next.config.ts # rewrites, image remote patterns, serverActions size
├── types/ # ambient declarations (plotly, react-map-gl)
└── scripts/ # UID-check and redirect-test helpers- app/ — the App Router tree; route groups separate localized content from the data catalogue and API routes.
- customtypes/ — the Prismic content model: page, news, event, public_disclosure, layout, employee, category, content_data, custom_search_data, custom_redirects, and the five menu types.
- slices/ — the reusable slice library consumed by Slice Machine and the SliceZone.
- src/ — shared source: the Prismic client config, feature components, i18n, hooks, and utilities.
- middleware.ts — the redirect engine described above.
Content Model
Uos.is has no relational database. All editorial content lives in Prismic, while a few runtime files are stored as local JSON in src/data/.
Custom type | Purpose |
|---|---|
page | Generic slice-composed page, resolved at /:uid |
news | News articles, localized (/frettir/:uid, /en/news/:uid) |
event | Events (/vidburdir/:uid, /en/event/:uid) |
public_disclosure | Public disclosure documents (/opinber-birting/:uid, /en/public-disclosure/:uid) |
layout | Site layout with Navbar/Footer/Children slices |
employee | Staff directory entries |
category | Taxonomies for news/events/public-disclosure |
content_data | Free-form slice content bundles |
custom_search_data | Hand-curated search suggestions (title/keyword/link/description) |
custom_redirects | CMS-managed redirects read by middleware.ts |
*_menu (×5) | Administration, Electricity, Energy, Environment, HR mega-menu definitions |
Subscriber state is kept outside Prismic: src/data/subscriptions.json (and backups) hold email-alert subscriptions, while src/data/email-subscriptions.json tracks the newsletter list.
Impact & Results
- In production as the public website of Orkustofnun, the National Energy Authority of Iceland — publishing news, disclosures, regulations, and an open data catalogue.
- Serves a national institution with bilingual content, full-text search (Algolia), and map-based data pages.
My Contributions
I was the lead developer on this project (627 commits). Key work:
- Built the bilingual routing layer — the (routes)/[locale] route groups, the is/en route resolver, and the middleware.ts locale-aware redirect engine that reads custom_redirects from Prismic and maps slugs between Icelandic and English routes.
- Implemented the Algolia search experience: the /api/algolia/sync-pages sync route, date-sorted search results, and search suggestions enriched with type and date info.
- Added the per-layout theming system (layoutUid context) so slices like News/PublicDisclosure can restyle themselves for alternate layouts such as the Roe theme, using theme color variables.
- Built and refined slice UI: card-grid button variations, RichText tooltip/link/id-attribute support, and search loading skeletons, plus fixes for hydration mismatches in Hero V2 and the search page.
- Wired the email-alert flow (subscribe/confirm/unsubscribe/status API routes) against SendGrid lists.
References
[1] Orkustofnun, "National Energy Authority of Iceland," [Online]. Available: https://orkustofnun.is
[2] Next.js, "Next.js 15 Documentation," nextjs.org, 2025. [Online]. Available: https://nextjs.org/docs
[3] Prismic, "Prismic documentation — headless CMS," prismic.io, 2025. [Online]. Available: https://prismic.io/docs
[4] Algolia, "Algolia Search API documentation," algolia.com, 2025. [Online]. Available: https://www.algolia.com/doc/
[5] SendGrid (Twilio), "SendGrid Email API," twilio.com, 2025. [Online]. Available: https://sendgrid.com
[6] Mapbox, "Mapbox GL JS documentation," mapbox.com, 2025. [Online]. Available: https://docs.mapbox.com/mapbox-gl-js/
Comments
Leave a Comment
You must be signed in to comment
0 Comments
No comments yet. Be the first to comment!