File-based Astro routing for the public site. Each .astro or .ts file becomes a URL route. All pages use export const prerender = false for on-demand SSR. Dynamic routes use bracket syntax ([slug], [isbn]). Pages cache aggressively via Cache-Control headers set in Astro.response.headers.
pages/
├── index.astro - Homepage: hero search bar + curated book sections
├── 404.astro - Custom 404 error page
├── [slug].astro - Catch-all for CMS-managed static content pages
├── nyheter.astro - New arrivals listing
├── sok.astro - Full-text book search (query via ?q=)
├── annonssamarbeten.astro - Advertising partnerships information
├── branschkollegor.astro - Industry colleague resources page
├── forlagstjanster.astro - Publisher services page
├── integritetspolicy.astro - Privacy policy
├── kontakt.astro - Contact information page
├── om-oss.astro - About Smakprov.se
├── sa-gor-vi-smakprov.astro - How PDF samples are produced
├── smakprov-express.astro - Express publisher service page
├── sitemap-books.xml.ts - Dynamic XML sitemap for all book pages (queries DB)
├── smakprov.php.ts - Legacy PHP redirect compatibility endpoint
├── admin/
│ ├── index.astro - Admin dashboard (auth required)
│ ├── login.astro - Admin login page
│ ├── analytics.astro - Page-view analytics dashboard
│ ├── navigation.astro - CMS navigation editor
│ ├── categories/
│ │ ├── index.astro - Category list
│ │ ├── [id].astro - Edit category
│ │ └── new.astro - Create category
│ ├── content/
│ │ ├── index.astro - CMS content block list
│ │ └── [key].astro - Edit content block by key
│ ├── curated/
│ │ ├── index.astro - Curated list manager
│ │ ├── [id].astro - Edit curated list
│ │ └── new.astro - Create curated list
│ ├── groups/
│ │ ├── index.astro - Category group manager
│ │ ├── [id].astro - Edit category group
│ │ └── new.astro - Create category group
│ └── partners/
│ ├── index.astro - Partner list
│ ├── [slug].astro - Edit partner
│ └── new.astro - Create partner
├── api/
│ ├── admin/
│ │ ├── analytics.ts - Admin analytics API: reads page_views
│ │ ├── categories.ts - CRUD API for cms_categories
│ │ ├── content.ts - CRUD API for cms_content
│ │ ├── curated.ts - CRUD API for cms_curated_lists
│ │ ├── groups.ts - CRUD API for cms_category_groups
│ │ ├── navigation.ts - API for navigation configuration
│ │ └── partners.ts - CRUD API for partners
│ ├── analytics/
│ │ └── collect.ts - Endpoint that receives page view events (inserts into page_views)
│ └── auth/
│ ├── login.ts - Admin login endpoint (sets session cookie)
│ └── logout.ts - Admin logout endpoint (clears session)
├── bok/
│ └── [slug].astro - Book detail page (SSR, 301 redirect from ISBN URLs)
├── blogg/
│ ├── index.astro - Blog post listing
│ └── [...slug].astro - Individual MDX blog post
├── forlag/
│ ├── [slug].astro - Publisher catalog page
│ └── [slug]/portal/
│ └── statistik.astro - Publisher portal: statistics view
├── kategori/
│ └── [slug].astro - Category browse with paginated BookGrid
├── lab/
│ └── allBooksWithSmakprov_hum.php.ts - Legacy PHP feed compatibility endpoint
├── las/
│ └── [isbn].astro - Full-screen PDF reader (noindex, client-only React)
├── native/
│ ├── index.astro - Native (sponsored) content listing
│ └── [...slug].astro - Individual native content article
└── smakprov/
├── index.astro - Smakprov landing/browse page
└── visa/[isbn]/partner/
└── [slug].astro - Partner-branded smakprov reader page
@/lib/supabase — Supabase client for all data fetching (anon key on public pages; service role key on admin API routes)@/lib/types — mapBookWithRelations(), getAuthorString()@/lib/categories — getCategoryBySlug(), categories[]@/lib/curated — getCuratedBooks() for homepage sections@/layouts/* — BaseLayout, BookLayout, BlogLayout, AdminLayout@/components/* — BookCard, BookGrid, BuyLinks, CuratedSection, Pagination, etc.@/components/admin/* — React components for the CMS admin UI@/components/pdf/PdfViewer — Client-only React PDF readersrc/middleware.ts — Session authentication check for admin routes/ — index.astroFetches curated sections from getCuratedBooks() (one Supabase query per section). Caches for 1 hour with 10 minute stale-while-revalidate.
/bok/[slug] — bok/[slug].astroPrimary book page. If the URL param looks like a 13-digit ISBN, issues a 301 redirect to the slug-based URL. Fetches the book with full join (publishers, book_contributors, book_categories). Loads similar books by shared category IDs, falling back to same publisher. Caches for 24 hours.
/las/[isbn] — las/[isbn].astroFull-screen PDF reader. Validates the ISBN param, looks up storage_url, renders <PdfViewer client:only="react">. Marked noindex. Caches for 24 hours.
/kategori/[slug] — kategori/[slug].astroFilters books by category slug using the filters array from lib/categories.ts. Matches against categories.name with type genre or subject. Supports pagination via ?page= query param.
/sok — sok.astroAccepts ?q= query parameter. Searches books table using Supabase .ilike() across title, ISBN, and author name fields.
/forlag/[slug] — forlag/[slug].astroPublisher catalog page. Lists all books by the given publisher. The portal sub-route (/forlag/[slug]/portal/statistik) shows page-view statistics for the publisher’s books, gated behind authentication.
/native/[...slug] and /blogg/[...slug]Server-rendered MDX content pages. native/ serves sponsored/partner content; blogg/ serves editorial blog posts. Both use the Astro content collection system with MDX.
/smakprov/visa/[isbn]/partner/[slug] — smakprov/visa/[isbn]/partner/[slug].astroPartner-branded reader page. Loads partner branding from the partners table and renders the PDF viewer with partner header. Used for affiliate/partner deep-links.
/admin/*Admin CMS pages protected by src/middleware.ts. All admin pages import AdminLayout. The React components in components/admin/ handle interactive client-side operations (toast messages, CRUD forms, category picker). Admin API routes are in api/admin/ and use the Supabase service role key.
/api/analytics/collect — collect.tsReceives POST requests with page view data and inserts a row into page_views. Called client-side by Analytics.astro. No auth required — uses the anon key. Rate limiting should be handled at the CDN or WAF level.
/sitemap-books.xml.tsGenerates an XML sitemap by paginating through the books table and emitting one <url> per book slug. Used alongside the @astrojs/sitemap static sitemap.
// Standard content page — 1 hour TTL
Astro.response.headers.set('Cache-Control', 'public, s-maxage=3600, stale-while-revalidate=600');
// Book/reader pages — 24 hour TTL
Astro.response.headers.set('Cache-Control', 'public, s-maxage=86400, stale-while-revalidate=3600');