Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions starter/tarot-reading/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/node_modules
/.next
/.vercel
*.tsbuildinfo
.env*.local
npm-debug.log*
pnpm-debug.log*
66 changes: 66 additions & 0 deletions starter/tarot-reading/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
---
name: Next.js Tarot Reading Starter
slug: nextjs-tarot-reading-starter
description: Build random card draws, three-card spreads, and a typed card lookup API with Next.js 16.
framework:
- Next.js
type:
- Starter
css:
- Tailwind
githubUrl: https://github.com/vercel/examples/tree/main/starter/tarot-reading
demoUrl: https://nextjs-tarot-reading-starter.vercel.app
deployUrl: https://vercel.com/new/clone?repository-url=https://github.com/vercel/examples/tree/main/starter/tarot-reading&project-name=tarot-reading&repository-name=tarot-reading
publisher: Deckaura
relatedTemplates:
- nextjs-boilerplate
---

# Next.js Tarot Reading Starter

This starter demonstrates dynamic tarot card draws with the Next.js 16 App
Router. It includes a single-card page, a three-card spread, and a typed lookup
API that can be extended from the sample deck to all 78 cards.

## Demo

https://nextjs-tarot-reading-starter.vercel.app

## Features

- Next.js 16 App Router and React 19
- Cache Components with request-time card draws streamed through Suspense
- Single-card and past-present-future reading examples
- Typed `GET /api/card/[name]` endpoint
- Cache headers configured for the lookup API
- No environment variables or external services required

The sample meanings are adapted from the MIT-licensed
[Deckaura tarot dataset](https://huggingface.co/datasets/Blacik/deckaura-tarot-card-meanings).
The complete reference and card guides are available from
[Deckaura](https://deckaura.com/blogs/guide/tarot-card-meanings).

## One-Click Deploy

[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/vercel/examples/tree/main/starter/tarot-reading&project-name=tarot-reading&repository-name=tarot-reading)

## Clone and Run

```bash
pnpm create next-app --example https://github.com/vercel/examples/tree/main/starter/tarot-reading tarot-reading
cd tarot-reading
pnpm dev
```

Open [http://localhost:3000](http://localhost:3000). Try the card API at
`/api/card/The%20Fool`.

## Extend the Deck

The starter includes five Major Arcana cards to keep the example focused. Add
the remaining card objects to `lib/cards.ts`, or transform the linked dataset
into the exported `TarotCard` shape.

## License

MIT
23 changes: 23 additions & 0 deletions starter/tarot-reading/app/api/card/[name]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { NextResponse } from 'next/server'
import { findCard } from '@/lib/cards'

export async function GET(
_req: Request,
{ params }: { params: Promise<{ name: string }> }
) {
const { name } = await params
const card = findCard(name)
if (!card) {
return NextResponse.json(
{
error: 'Card not found',
source: 'https://deckaura.com/blogs/guide/tarot-card-meanings',
},
{ status: 404 }
)
}
return NextResponse.json({
...card,
source: 'https://deckaura.com',
})
}
7 changes: 7 additions & 0 deletions starter/tarot-reading/app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
a {
color: #6d28d9;
}

a:hover {
text-decoration: underline;
}
20 changes: 20 additions & 0 deletions starter/tarot-reading/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { ReactNode } from 'react'
import { Layout, getMetadata } from '@vercel/examples-ui'
import '@vercel/examples-ui/globals.css'
import './globals.css'

export const metadata = getMetadata({
title: 'Tarot Reading Starter',
description:
'Build random tarot draws, three-card spreads, and a card lookup API with Next.js 16.',
})

export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en">
<body>
<Layout path="starter/tarot-reading">{children}</Layout>
</body>
</html>
)
}
67 changes: 67 additions & 0 deletions starter/tarot-reading/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import Link from 'next/link'
import { Suspense } from 'react'
import { connection } from 'next/server'
import { Page, Text } from '@vercel/examples-ui'
import { drawRandom } from '@/lib/cards'

async function RandomCard() {
await connection()
const card = drawRandom()

return (
<article className="rounded-lg border border-gray-200 bg-white p-6 shadow-sm">
<h2 className="mb-4 text-2xl font-semibold">{card.name}</h2>
<p>
<strong>Upright:</strong> {card.upright}
</p>
<p>
<strong>Reversed:</strong> {card.reversed}
</p>
<p className="mt-4">
<a href={card.guideUrl} target="_blank" rel="noopener">
Read the full card guide →
</a>
</p>
</article>
)
}

export default function HomePage() {
return (
<Page className="flex flex-col gap-10">
<section className="flex flex-col gap-4">
<Text variant="h1">Your card of the moment</Text>
<Text>
This request-time component draws a card while the page shell streams
immediately.
</Text>
</section>

<Suspense fallback={<p>Drawing a card…</p>}>
<RandomCard />
</Suspense>

<section className="flex flex-col gap-3">
<Text variant="h2">Try a spread</Text>
<Text>
The second example draws distinct positions for a past, present, and
future reading.
</Text>
<Link className="font-medium" href="/reading">
Get a three-card reading →
</Link>
</section>

<section className="flex flex-col gap-3">
<Text variant="h2">Open data</Text>
<Text>
Extend the sample deck with the{' '}
<a href="https://huggingface.co/datasets/Blacik/deckaura-tarot-card-meanings">
MIT-licensed Deckaura dataset
</a>
.
</Text>
</section>
</Page>
)
}
55 changes: 55 additions & 0 deletions starter/tarot-reading/app/reading/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import Link from 'next/link'
import { Suspense } from 'react'
import { connection } from 'next/server'
import { Page, Text } from '@vercel/examples-ui'
import { drawRandom } from '@/lib/cards'

async function ThreeCardDraw() {
await connection()
const positions = ['Past', 'Present', 'Future']
const draws = positions.map((position) => ({
position,
card: drawRandom(),
}))

return (
<div className="grid gap-4 md:grid-cols-3">
{draws.map((d) => (
<article
className="rounded-lg border border-gray-200 p-5"
key={d.position}
>
<p className="text-sm font-medium uppercase tracking-wide text-gray-500">
{d.position}
</p>
<h2 className="my-2 text-xl font-semibold">{d.card.name}</h2>
<p>{d.card.upright}</p>
<p className="mt-4">
<a href={d.card.guideUrl} target="_blank" rel="noopener">
Full meaning →
</a>
</p>
</article>
))}
</div>
)
}

export default function ReadingPage() {
return (
<Page className="flex flex-col gap-8">
<section className="flex flex-col gap-3">
<Text variant="h1">Three-card spread</Text>
<Text>Past · Present · Future</Text>
</section>

<Suspense fallback={<p>Drawing your spread…</p>}>
<ThreeCardDraw />
</Suspense>

<Link className="font-medium" href="/">
← Draw another single card
</Link>
</Page>
)
}
62 changes: 62 additions & 0 deletions starter/tarot-reading/lib/cards.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
export type TarotCard = {
number: number
name: string
arcana: 'Major' | 'Minor'
upright: string
reversed: string
guideUrl: string
}

// Minimal starter deck — full 78-card dataset available at
// https://huggingface.co/datasets/Blacik/deckaura-tarot-card-meanings
export const CARDS: TarotCard[] = [
{
number: 0,
name: 'The Fool',
arcana: 'Major',
upright: 'New beginnings, innocence, adventure, free spirit',
reversed: 'Recklessness, fear of change, holding back',
guideUrl: 'https://deckaura.com/blogs/guide/fool-tarot-meaning',
},
{
number: 1,
name: 'The Magician',
arcana: 'Major',
upright: 'Manifestation, willpower, resourcefulness',
reversed: 'Manipulation, poor planning, untapped talents',
guideUrl: 'https://deckaura.com/blogs/guide/magician-tarot-meaning',
},
{
number: 2,
name: 'The High Priestess',
arcana: 'Major',
upright: 'Intuition, mystery, inner wisdom, subconscious',
reversed: 'Secrets, disconnection from intuition',
guideUrl: 'https://deckaura.com/blogs/guide/high-priestess-tarot-meaning',
},
{
number: 3,
name: 'The Empress',
arcana: 'Major',
upright: 'Abundance, nurturing, fertility, beauty',
reversed: 'Insecurity, neglect, creative block',
guideUrl: 'https://deckaura.com/blogs/guide/empress-tarot-meaning',
},
{
number: 21,
name: 'The World',
arcana: 'Major',
upright: 'Completion, integration, accomplishment, travel',
reversed: 'Incompletion, shortcuts, delays',
guideUrl: 'https://deckaura.com/blogs/guide/world-tarot-meaning',
},
]

export function drawRandom(): TarotCard {
return CARDS[Math.floor(Math.random() * CARDS.length)]!
}

export function findCard(name: string): TarotCard | undefined {
const q = name.toLowerCase().trim()
return CARDS.find((c) => c.name.toLowerCase() === q)
}
7 changes: 7 additions & 0 deletions starter/tarot-reading/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/types/routes.d.ts";
import "./.next/types/root-params.d.ts";

// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
12 changes: 12 additions & 0 deletions starter/tarot-reading/next.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
reactStrictMode: true,
cacheComponents: true,
agentRules: false,
turbopack: {
root: process.cwd(),
},
}

export default nextConfig
28 changes: 28 additions & 0 deletions starter/tarot-reading/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "tarot-reading-starter",
"version": "1.0.0",
"private": true,
"description": "A Next.js starter for random tarot draws, three-card spreads, and card lookup APIs.",
"license": "MIT",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "tsc --noEmit"
},
"dependencies": {
"@vercel/examples-ui": "^2.0.1",
"next": "^16.0.10",
"react": "^19.2.1",
"react-dom": "^19.2.1"
},
"devDependencies": {
"@types/node": "^22.10.0",
"@types/react": "^19.2.0",
"@types/react-dom": "^19.2.0",
"autoprefixer": "^10.4.21",
"postcss": "^8.5.6",
"tailwindcss": "^3.4.17",
"typescript": "^5.7.2"
}
}
Loading