Serrano.is



Overview
Serrano.is is the customer-facing ordering and marketing website for Serrano, an Icelandic Mexican food chain. Visitors can customize burritos and other menu items, filter by allergens and food intolerances, join a loyalty program with QR-code rewards, and order for pickup or delivery — in Icelandic or English.
As with the other VISKA customer sites, Prismic manages all marketing content and the menu layout, while the product catalog, loyalty ledger, cart, and checkout come from the shared Upsell platform via @upsell/kit and the public-api.upsell.is/v2 API. There is no local database; SWR handles client-side data fetching and revalidation.
Tech Stack & Rationale
Layer | Technology | Why |
|---|---|---|
Framework | Next.js 14 (App Router), React 18 | Server-first rendering with locale route groups and generateMetadata |
Language | TypeScript | Strict typing across slices, kit types, and API responses |
Styling | Tailwind CSS 3.4 | Brand styling with the Serrano orange theme |
CMS | Prismic + Slice Machine | Content editors manage pages, layout, and 21 slices |
Client data fetching | SWR 2.2 | Revalidation for cart, checkout, and loyalty state |
Business logic | @upsell/kit + public-api.upsell.is/v2 (Axios) | Shared cart/checkout plus loyalty-point redemption |
Loyalty | react-qr-code | Renders the member QR code scanned in-store |
i18n | LinguiJS (is, en) + Crowdin | English-first msgids localized to Icelandic |
Maps | Mapbox GL + react-map-gl | Store/location map with opening hours |
Animation | GSAP, Framer Motion, Swiper, Slick | Hero motion and carousels |
Monitoring | Sentry | Production error tracking |
Hosting | Cloudflare Workers via OpenNext (migrated from Vercel) | Edge deployment of the standalone build |
Architecture
A request is locale-normalized by middleware, server-rendered from Prismic content, then enriched client-side with catalog and loyalty data from the Upsell API. A dedicated loyalty-transfer API route bridges the QR member card to the ordering platform.
The flow runs: Browser request (/is/…) → middleware.ts (locale redirect → is) → Server Components (Prismic pages + slices) → SWR hooks (client revalidation) → public-api (upsell.is/v2); Browser request (/is/…) → middleware.ts (locale redirect → is) → Prismic client (tag cache + preview) → @upsell/kit (cart · checkout) → loyalty API (/api/loyalty-transfer).
- Locale routing — middleware.ts redirects locale-less requests to /is/... and sets a locale header, skipping /api/ and /.well-known.
- Server rendering — (main) pages are React Server Components that fetch Prismic pages documents (with their linked layouts) through a tag-cached client and render the slice zone; generateMetadata builds SEO tags from each document's metadata.
- Client interactivity — the product/deal customizer and menu hydrate into client components backed by @upsell/kit, with SWR managing cart state; allergen and intolerance categories auto-hide when they contain zero products.
- Loyalty — the member QR code is rendered with react-qr-code; a serverless /api/loyalty-transfer route bridges the member account to the ordering platform, and checkout displays partial loyalty-point redemption.
- Edge serving — the standalone build runs on Cloudflare Workers via OpenNext, with a staticPageGenerationTimeout for API-fetching pages and Sentry active in production.
Folder Structure
customers/serrano-web/
├── app/
│ ├── [locale]/
│ │ ├── (main)/
│ │ │ ├── (customization)/ # products/[productID], deals/[dealID]
│ │ │ ├── staerri-pantanir/ # larger/catering orders
│ │ │ ├── njottu-dagsins/ # deal of the day
│ │ │ ├── store/ · profile/ · login/
│ │ ├── (clean)/checkout/ # shipping · summary · verification
│ │ └── (prismic)/[[...slug]]/ # CMS pages + slice-simulator
│ ├── api/ # preview, revalidate, loyalty-transfer
│ └── sitemap.ts · robots.ts · manifest.ts
├── components/ # allergens, cart, checkout, deal, menu, nutrition, product…
├── slices/ # 21 slices (OurMenu, LoyaltyCard, AllergensInformation, …)
├── customtypes/ # pages, layouts, top_banner
├── context/ · constants/ # branch/product constants, context providers
├── lib/ # prismic.ts, api.ts, linkResolver.ts, getAttributePrice.ts, hooks/
├── locales/translations/ # en.po, is.po (Crowdin)
└── middleware.ts · appRouterI18n.ts · prismicio.tsContent Model
Prismic is the sole content source. Three custom types drive the site:
Custom type | Kind | Purpose |
|---|---|---|
pages | Repeatable | Marketing pages — uid, page_title, a layout link, a slice zone, and SEO metadata |
layouts | Repeatable | Site chrome composed of navbar, children, and footer slices; linked from each pages document |
top_banner | Singleton | An optional promo bar (paragraph, cta_text, cta_url, active) surfaced by useTopBanner |
The pages slice zone exposes 19 slices, including our_menu, featured_products, nutrition_information, allergens_information, food_intolerance, loyalty_card, about_us, location_info, and form slices (contact_form, job_aplication_form, partnership_form), plus three hero variants (hero, orange_hero, simple_orange_hero).
Impact & Results
- In production for the Serrano Mexican chain in Iceland, with QR loyalty, allergen filtering, and bilingual ordering.
- One of 16+ customer sites served by the shared VISKA monorepo platform.
My Contributions
Worked on the Serrano.is site as part of the VISKA monorepo team:
- Migrated the deployment from Vercel to Cloudflare Workers via OpenNext.
- Built the loyalty experience — the QR member card, the loyalty-transfer API route, and partial loyalty-point redemption in checkout.
- Implemented category filtering and deals display improvements, and auto-hiding of nutrition/allergen categories with zero products.
- Added opening hours to the locations page and standardized i18n msgids to English for Crowdin-based multilingual support.
- Fixed the sitemap URL generation, toggle-button contrast on checkout, and native-fetch metadata for the staerri-pantanir pages.
References
[1] Serrano, "Serrano.is," serrano.is, 2025. [Online]. Available: https://serrano.is
[2] Vercel, "Next.js Documentation," nextjs.org. [Online]. Available: https://nextjs.org/docs
[3] Prismic, "Prismic Documentation," prismic.io. [Online]. Available: https://prismic.io/docs
[4] Vercel, "SWR — React Hooks for Data Fetching," swr.vercel.app. [Online]. Available: https://swr.vercel.app
[5] LinguiJS, "LinguiJS — Internationalization," lingui.dev. [Online]. Available: https://lingui.dev
Comments
Leave a Comment
You must be signed in to comment
0 Comments
No comments yet. Be the first to comment!