Skip to content
Merged
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
29 changes: 29 additions & 0 deletions public/llms.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# CardLedger

> A local-first, high-performance Pokémon TCG catalog and portfolio manager. Browse every English-language Pokémon card, track market prices, and build a personal collection portfolio — all with instant, client-side search and offline support.

## Key pages

- [Home](https://www.cardledger.io/): Search across the full Pokémon TCG catalog with instant fuzzy results
- [Sets](https://www.cardledger.io/sets): Browse all Pokémon TCG sets with release dates and card counts
- [Cards](https://www.cardledger.io/cards): Individual card detail pages with pricing history, market stats, and set info
- [About](https://www.cardledger.io/about): Information about the project, data sources, and features

## Data available

- Complete English Pokémon TCG card catalog with images
- TCG set listings with release dates, symbols, and card counts
- Market pricing data sourced from TCGPlayer
- Historical price charts per card
- Card details: HP, types, attacks, abilities, weaknesses, resistances, retreat cost, rarity, and illustrator

## API

All routes under `/api/` are internal and not intended for public consumption.

## Technical notes

- Built with Next.js (App Router), React, Prisma, and PostgreSQL
- Uses a service worker for offline-first caching of visited pages
- Card search powered by client-side fuzzy matching for instant results
- Images served from a dedicated CDN at assets.cardledger.io
24 changes: 0 additions & 24 deletions public/robots.txt

This file was deleted.

45 changes: 41 additions & 4 deletions src/app/cards/[cardId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,48 @@ import { CardBreadcrumbs } from './CardBreadcrumbs';
import { ClientCachedBreadcrumbFallback } from './ClientCachedBreadcrumbFallback';
import { ClientCachedDetailsFallback } from './ClientCachedDetailsFallback';
import { PriceHero } from '@/src/components/cards/PriceHero';
import { Metadata } from 'next';
import { getCachedCardData } from './data';

// src/app/cards/[cardId]/page.tsx
// Thin server shell — no data fetching here.
// All card data comes from client-side zustand stores (hydrated from IndexedDB/R2)
// for instant navigation with zero loading skeletons.
export async function generateMetadata({
params
}: {
params: Promise<{ cardId: string }>;
}): Promise<Metadata> {
const { cardId } = await params;
const card = await getCachedCardData(cardId);

if (!card) {
return {
title: 'Card Not Found | CardLedger',
description: 'The requested card could not be found.'
};
}

const cardName = card.n;
const cardNumber = card.num;
const setName = card.set.name;
const title = `${cardName} #${cardNumber} (${setName}) | CardLedger`;
const description = `View ${cardName} #${cardNumber} from the ${setName} set. Track prices, check market trends, and add to your Pokémon TCG collection on CardLedger.`;

return {
title,
description,
alternates: {
canonical: `/cards/${cardId}`
},
openGraph: {
title,
description,
type: 'website'
},
twitter: {
card: 'summary',
title,
description
}
};
}

export default async function SingleCardPage({ params, searchParams }: {
params: Promise<{ cardId: string }>;
Expand Down
1 change: 1 addition & 0 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const APP_TITLE_TEMPLATE = '%s | CardLedger';
const APP_DESCRIPTION = 'A local-first, high-performance Pokémon TCG catalog and portfolio manager.';

export const metadata: Metadata = {
metadataBase: new URL('https://www.cardledger.io'),
applicationName: APP_NAME,
title: {
default: APP_DEFAULT_TITLE,
Expand Down
39 changes: 39 additions & 0 deletions src/app/robots.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { MetadataRoute } from 'next'

export default function robots(): MetadataRoute.Robots {
return {
rules: [
{
userAgent: '*',
allow: '/',
disallow: '/api/',
},
// Block known aggressive commercial scrapers
{
userAgent: 'AhrefsBot',
disallow: '/',
},
{
userAgent: 'SemrushBot',
disallow: '/',
},
{
userAgent: 'MJ12bot',
disallow: '/',
},
{
userAgent: 'DotBot',
disallow: '/',
},
{
userAgent: 'PetalBot',
disallow: '/',
},
{
userAgent: 'barkrowler',
disallow: '/',
},
],
sitemap: 'https://www.cardledger.io/sitemap.xml',
}
}
5 changes: 4 additions & 1 deletion src/app/sets/[setId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ export async function generateMetadata({

return {
title: `${set.name} | CardLedger`,
description: `Browse all ${set.printedTotal} cards from the ${set.name} set.`
description: `Browse all ${set.printedTotal} cards from the ${set.name} set.`,
alternates: {
canonical: `/sets/${setId}`
}
};
}

Expand Down
2 changes: 1 addition & 1 deletion src/app/sitemap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { MetadataRoute } from 'next'
import { prisma } from '@/src/lib/prisma';

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const baseUrl = 'https://cardledger.io'
const baseUrl = 'https://www.cardledger.io'

const cards = await prisma.card.findMany({
select: { id: true, releaseDate: true }
Expand Down
Loading