AV
HomeAboutProjectBlog

© 2026 Ave syah Shina. All rights reserved.

  1. Home
  2. Projects
  3. Serrano.is

Serrano.is

June 5, 20254 min read
View Project
Next JsTypescriptTailwind CSSSWRPrismic
1 / 3

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).

  1. Locale routing — middleware.ts redirects locale-less requests to /is/... and sets a locale header, skipping /api/ and /.well-known.
  2. 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.
  3. 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.
  4. 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.
  5. 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.ts

Content 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!