manhattan.is
Off Squarespace, onto a site of their own.
Overview
Manhattan is a marketing, management and advisory firm in Reykjavík. The old site ran on a Squarespace template. The new one is built from scratch: over , bilingual, sixty-two pre-rendered pages. The consultants run their own projects, articles and enquiries from an Icelandic-language admin panel. The brand guide is implemented in the code itself, so the colours, the type and the pattern come from there rather than from a template.
Consulting · Marketing
This is just a screenshot, better to view the live site: manhattan.is
This is just a screenshot, better to view the live site: manhattan.is
This is just a screenshot, better to view the live site: manhattan.is
This is just a screenshot, better to view the live site: manhattan.is
This is just a screenshot, better to view the live site: manhattan.is
This is just a screenshot, better to view the live site: manhattan.is
This is just a screenshot, better to view the live site: manhattan.is
Problem
The site ran on a Squarespace template. Eighteen URLs were live and fourteen of them had no equivalent in the new structure, so they would 404 on launch and take their ranking with them. The brand guide never made it all the way into the template: navy, cream and five accents. Access was the worse problem: nobody at Manhattan could get into the AWS account holding the domain's DNS. Changing a single record meant moving the whole zone.
Approach
with the over , row-level security on every table. Nothing in the browser talks to : sign-in, enquiries and every mutation go through . The admin panel requires two-factor and splits people into editors and admins. The brand guide is written as Tailwind tokens, so one accent governs each page and no component holds a colour of its own. The fourteen legacy URLs each got a permanent redirect to the page that answers the same question, and further are added from the panel. Accessibility was measured with axe-core on every route across both languages, both themes and two widths, with hand-written checks covering what axe cannot see.
Outcome
Sixty-two pre-rendered pages in two languages, live on . The consultants write articles and add projects themselves, and enquiries from the contact form land both in an inbox and in the panel. GA4 traffic is reported inside the admin panel, so nobody needs a separate Google login to see how the site is doing. The accessibility statement at /adgengi rests on tests anyone can run again. If the database goes down, the site still ships from a frozen copy of the content.
The hosting register
The project started with one DNS record nobody could reach. So the site ships with a register, inside the admin panel, of where everything is hosted: , GitHub, , the domain registrar, email and analytics. Each entry says what the service does, who owns the account and how the next employee gets in. No passwords and no keys, just the way in. It is the cheapest insurance against the firm being locked out again.
Key features
- 01Bilingual site, Icelandic by default and English under /en
- 02The database refuses a half-translated row, so the two languages stay in step
- 03Sixty-two pre-rendered pages, no wait on first paint
- 04Icelandic-language admin for projects, articles and enquiries
- 05Mandatory two-factor with editor and admin roles
- 06A rich-text editor with drafts and publishing, images in
- 07Dark mode that remembers the choice and never flashes
- 08 2.2 AA, verified with axe-core and hand-written checks
- 09GA4 traffic reported inside the admin panel
- 10Fourteen permanent off the old Squarespace URLs
- 11The owner can let people back in themselves when email fails
Security
- 01Nothing in the browser talks to , neither sign-in nor queries
- 02Mandatory two-factor () on every admin account
- 03A backup authenticator is enrolled from inside an session, never from a password alone
- 04Row-level security on every table, the role decides what is visible
- 05The key is used in three places and never in anything that renders
- 06An audit log of what admins do
- 07A strict , and COOP on every response
- 08The contact form carries a honeypot and five submissions per hour per IP
- 09The retention rule lives in the database: IP and browser details drop after 90 days, the row after two years
- 10The Meta pixel loads only after marketing consent, and only on the live domain
- 11Data hosted in Stockholm, inside the EEA
Accessibility
- 01 2.2 AA across every route, both languages and both themes
- 02axe-core run on every route × locale × theme × width
- 03Focus never hidden behind the sticky header, checked by hit-testing (2.4.11)
- 04Focus visibility measured under real Tab presses, not programmatic focus (2.4.7)
- 05Reflow at 320 px and text resize to 200% (1.4.10 and 1.4.4)
- 06Contrast over photography sampled from rendered pixels (1.4.3)
- 07Animation stops when the OS asks for reduced motion (2.3.3)
- 08Projects reorder by drag or by button, so the keyboard alone is enough
- 09A public accessibility statement at /adgengi that the tests back up
Search & migration
- 01Fourteen permanent 308s, each to the page that answers the old page's question
- 02Database-backed the admin adds directly, applied in middleware
- 03 and canonical on every page in both languages
- 04 schemas (MarketingAgency, Article, WebSite, BreadcrumbList)
- 05The Facebook page is linked in the footer and declared in sameAs, both from one list
- 06sitemap.xml and robots.txt generated from the content itself
- 07 pings for Bing and Yandex
- 08Share cards drawn with , a separate card per article
- 09The bare apex is canonical and www to it, so signals never split
By the numbers
- 62
- pre-rendered pages
- 14
- legacy URLs redirected
- AA
- WCAG 2.2, verified
Stack
- Next.js 16 App Router
- React 19
- React Compiler
- TypeScript
- Tailwind CSS v4
- next-intl
- Supabase Postgres
- Supabase Auth (TOTP MFA)
- Supabase Storage
- TipTap
- Resend
- Upstash Redis
- Google Analytics Data API
- Vercel
Related services



