Karma Yoga



Overview
Karma Yoga is the marketing website for Karma Yoga Studio, an Icelandic yoga studio at karmajogastudio.is. The site presents the studio's classes (námskeið), books (bækur), news (fréttir), and general pages (about, contact) in Icelandic. It was built by VISKA-IO as a client project, with content managed by non-technical editors through Prismic.
The core problem it solves is editorial independence: the studio team needs to publish and reorder course offerings, books, news posts, and page sections frequently without involving developers or redeploying the site. Prismic's Slice Machine model lets editors compose pages from reusable "slices" (hero, text-with-image, video, review, etc.), while the code stays a static Next.js app that fetches from the Prismic Delivery API and revalidates on content changes.
The primary users are prospective students and visitors in Iceland browsing the schedule and offerings, and secondarily the studio's content editors who assemble pages in the Prismic dashboard. The site also captures enquiries through a validated contact form delivered via SendGrid.
Tech Stack & Rationale
Layer | Technology | Why |
|---|---|---|
Framework | Next.js 14 (App Router) | Server components, static generation, and catch-all Prismic routing |
Language | TypeScript | Type safety across Prismic types and slice props |
Styling | Tailwind CSS 3 | Utility-first styling shared across all slices |
CMS | Prismic + Slice Machine | Headless, slice-based content with editor-friendly UI |
Prismic client | @prismicio/client / next / react | Delivery API queries, auto-previews, and SliceZone rendering |
Forms | Formik + Yup | Validation for the contact form |
SendGrid (@sendgrid/mail) | Contact-form delivery via an API route | |
SEO | next-sitemap + schema-dts | Sitemap generation and JSON-LD structured data |
Media | react-player, react-youtube, react-photo-album | Video and photo-gallery slices |
Architecture
The site follows the classic Prismic + Next.js App Router integration: a single catch-all route resolves a Prismic page document by UID, then renders its slices through SliceZone. Content is fetched server-side through a cached Prismic client; the contact form is the only interactive, write path.
The flow runs: Prismic CMS (documents + webhook) → createClient (cached, tag: prismic, force-cache); [[...slug]] page.tsx (getByUID('page', uid)) → SliceZone (renders page.data.slices); Slice components (Hero, Course, News, Video…) → Contact form (Formik/Yup, server action sendEmail()) → /api/sendgrid/send (SendGrid API route).
Data flow walkthrough:
- A request to a URL such as /um-okkur hits the catch-all route app/(routes)/(prismic)/[[...slug]]/page.tsx, which maps the slug to a Prismic page UID (home when empty).
- generateMetadata() and the page body both call createClient().getByUID('page', uid). In production the client uses next: { tags: ['prismic'], cache: 'force-cache' }, so results are cached and invalidated on demand.
- The page renders a SliceZone with the page's data.slices, resolving each slice through slices/index.ts (which lazy-loads the slice via next/dynamic).
- Slices such as Course, Book, and News are themselves thin server wrappers that query related documents (courses, books, news) via core/prismic/fetcher.ts, then hand data to a client component for filtering/interaction.
- Detail pages (baekur/[uid], frettir/[uid], namskeid/[uid]) resolve their own documents by UID and render dedicated layouts.
- The contact form submits through the sendEmail server action in core/utils/sendEmail.ts, which POSTs to /api/sendgrid/send; that route forwards the message to the studio mailbox via SendGrid.
- The Prismic webhook calls /api/revalidate to purge the prismic cache tag when editors publish changes.
Folder Structure
karma-yoga-studio/
├── app/ # Next.js App Router (routes + API handlers)
│ ├── (routes)/(prismic)/
│ │ ├── [[...slug]]/ # Catch-all page (Prismic `page` by UID)
│ │ ├── baekur/[uid]/ # Book detail pages (bækur)
│ │ ├── frettir/[uid]/ # News detail pages (fréttir)
│ │ └── namskeid/[uid]/ # Course detail pages (námskeið)
│ ├── api/
│ │ ├── preview/route.ts # Prismic draft preview
│ │ ├── exit-preview/route.ts
│ │ ├── revalidate/route.ts # Webhook cache invalidation
│ │ └── sendgrid/send/route.ts # SendGrid contact delivery
│ ├── slice-simulator/page.tsx # Slice Machine local simulator
│ ├── layout.tsx
│ └── icon.ico
├── components/ # Shared UI
│ ├── Book/ Course/ News/ # Feature-specific components
│ └── _shared/ # Button, Icons, VimeoPopup, YoutubePopup
├── core/ # Non-route app logic
│ ├── @types/ # Prismic type aliases
│ ├── data/ # Static data (SpaceData)
│ ├── lib/ # generateMetaData, formatNumber
│ ├── prismic/ # fetcher.ts, inject.ts
│ ├── styles/ # base/components/tailwind/utilities CSS
│ └── utils/ # useResize, prismicLinkResolver, sendEmail
├── customtypes/ # Prismic custom types
│ ├── page/ book/ course/ news/ layout/
├── slices/ # Slice Machine slices (13) + index.ts
├── public/ # Static assets
├── prismicio.ts # Prismic client + preview setup
├── slicemachine.config.json
├── next.config.mjs
├── next-sitemap.config.js
├── tailwind.config.ts
├── postcss.config.mjs
└── package.json- app/ holds every route and API handler; the (routes)/(prismic) group isolates the Prismic-driven pages.
- components/ contains reusable UI, split into feature folders and a _shared folder for primitives.
- core/ is the project's own "framework" layer: Prismic fetchers, metadata generation, styles, and utilities.
- customtypes/ defines the Prismic content models (page, book, course, news, layout) consumed by @prismicio/client.
- slices/ holds the Slice Machine slices, each with index.tsx, model.json, and mocks.json.
Content Model
The site's "database" is Prismic. It uses five custom types and thirteen slices.
Custom types
Type | Repeatable | Key fields |
|---|---|---|
page | yes | html_title, layout (link), slices (slice zone), meta_title, meta_description, meta_image |
course | yes | name, image, about, more_about, date, location, price, cta (group) |
book | yes | name, description, about, price, publication_year, language, cta_text/cta_link, image |
news | yes | title, thumbnail, about, text, cta_link, layout (link), slices |
layout | custom | slices limited to navigation_bar, children, footer |
Slices — hero, text_with_image, video, review, benefit, contact, course, book, news, navigation_bar, children, footer. The page type exposes the content slices; layout is reserved for chrome (navbar/children/footer) shared across pages via the layout link field.
Impact & Results
- In production since 2025 for Karma Yoga Studio (Iceland), letting non-technical editors publish classes, books, and news through Prismic without redeploys.
- Client deliverable shipped by VISKA-IO as a small team.
My Contributions
My work on this project:
- Configured the project foundation: metadata generator, next-sitemap, and ESLint/Prettier setup.
- Established the slice workflow and built the Footer slice for desktop; created and updated the Course slice and added four slices in total.
- Set up Tailwind theming and fonts used across the slices.
- Built the mobile responsive layouts for the contact ("hafa samband") and classes pages.
- Iterated on the Prismic client and content model, including image blur optimization and prismicio.ts updates.
- Fixed the video slice layout and contributed news slice updates.
References
[1] Next.js, "App Router Documentation," nextjs.org. [Online]. Available: https://nextjs.org/docs/app [2] Prismic, "Delivery API Client (@prismicio/client)," prismic.io. [Online]. Available: https://prismic.io/docs/technical-reference/prismicio-client [3] Prismic, "Slice Machine," prismic.io. [Online]. Available: https://prismic.io/slice-machine [4] SendGrid (Twilio), "SendGrid Mail Service for Node.js," sendgrid.com. [Online]. Available: https://docs.sendgrid.com/for-developers/sending-email/mail-service [5] Tailwind CSS, "Utility-first CSS framework," tailwindcss.com. [Online]. Available: https://tailwindcss.com/
Comments
Leave a Comment
You must be signed in to comment
0 Comments
No comments yet. Be the first to comment!