diff --git a/docs/superpowers/plans/2026-04-25-seo-and-readme.md b/docs/superpowers/plans/2026-04-25-seo-and-readme.md deleted file mode 100644 index c82e449..0000000 --- a/docs/superpowers/plans/2026-04-25-seo-and-readme.md +++ /dev/null @@ -1,616 +0,0 @@ -# SEO & README Implementation Plan - -> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Add comprehensive SEO infrastructure (metadata, OG images, sitemap, robots.txt, manifest) and update the README to reflect the current project state. - -**Architecture:** All SEO files live in `app/` as Next.js file conventions. The OG image is dynamically generated using `ImageResponse` from `next/og`, leveraging the existing `app/icon.png` logo. Sitemap and manifest use TypeScript file conventions for future dynamic data readiness. The site URL is `https://deckyvault.xyz`. - -**Tech Stack:** Next.js 16.2.4 App Router, `next/og` ImageResponse, TypeScript, Tailwind CSS v4 - ---- - -## File Structure - -### Files to Create -| File | Purpose | -|------|---------| -| `app/layout.tsx` | Modify: add full metadata (title template, description, OpenGraph, Twitter, authors, keywords, metadataBase) | -| `app/opengraph-image.tsx` | Create: dynamic OG banner image using ImageResponse with logo + brand colors | -| `app/twitter-image.tsx` | Create: Twitter card image (can share OG image logic or be identical) | -| `app/sitemap.ts` | Create: programmatic sitemap with static routes, ready for dynamic data | -| `app/robots.ts` | Create: robots.txt allowing all crawlers, pointing to sitemap | -| `app/manifest.ts` | Create: PWA web manifest with app name, theme colors, icons | -| `app/icon.png` | Already exists — used as favicon and in OG image generation | -| `README.md` | Modify: update tech stack, install commands, development status note | - -### Files Unchanged -| File | Reason | -|------|--------| -| `app/page.tsx` | Landing page — no SEO changes needed (metadata is in layout) | -| `app/globals.css` | Styles — no changes needed | -| `next.config.ts` | No config changes needed for this scope | -| `public/` | Stays empty — all assets handled via app directory conventions | - ---- - -## Chunk 1: Root Layout Metadata & OG Image Generation - -### Task 1: Enhance Root Layout Metadata - -**Files:** -- Modify: `app/layout.tsx` - -The current layout only sets `title`. We need to add full metadata for SEO and social sharing. - -- [ ] **Step 1: Update `app/layout.tsx` with comprehensive metadata** - -Replace the existing `metadata` export with a full configuration: - -```tsx -import type { Metadata } from "next" -import { Lexend } from "next/font/google" -import "./globals.css" - -const font = Lexend({ - variable: "--font-lexend", - subsets: ["latin"], -}) - -export const metadata: Metadata = { - metadataBase: new URL("https://deckyvault.xyz"), - title: { - default: "DeckyVault - Steam Deck Benchmarks & Settings", - template: "%s | DeckyVault", - }, - description: - "A fast, modern browser for finding game benchmarks, settings, and guides for Steam Deck OLED, Steam Deck LCD, and Steam Machine.", - keywords: [ - "Steam Deck", - "benchmarks", - "settings", - "performance", - "FPS", - "gaming", - "Steam Machine", - "Proton", - "FSR", - "compatibility", - ], - authors: [{ name: "Adrian Bonpin", url: "https://github.com/AdrianBonpin" }], - creator: "@adrianbonpin", - openGraph: { - type: "website", - locale: "en_US", - url: "https://deckyvault.xyz", - siteName: "DeckyVault", - title: "DeckyVault - Steam Deck Benchmarks & Settings", - description: - "A fast, modern browser for finding game benchmarks, settings, and guides for Steam Deck OLED, Steam Deck LCD, and Steam Machine.", - images: [ - { - url: "/opengraph-image", - width: 1200, - height: 630, - alt: "DeckyVault - Steam Deck Benchmarks & Settings", - }, - ], - }, - twitter: { - card: "summary_large_image", - title: "DeckyVault - Steam Deck Benchmarks & Settings", - description: - "A fast, modern browser for finding game benchmarks, settings, and guides for Steam Deck OLED, Steam Deck LCD, and Steam Machine.", - creator: "@adrianbonpin", - images: ["/twitter-image"], - }, - robots: { - index: true, - follow: true, - googleBot: { - index: true, - follow: true, - "max-video-preview": -1, - "max-image-preview": "large", - "max-snippet": -1, - }, - }, -} - -export default function RootLayout({ - children, -}: Readonly<{ - children: React.ReactNode -}>) { - return ( - - - {children} - - - ) -} -``` - -- [ ] **Step 2: Verify layout compiles** - -Run: `bun run build` -Expected: Build succeeds with no TypeScript errors. - ---- - -### Task 2: Create Dynamic OpenGraph Image - -**Files:** -- Create: `app/opengraph-image.tsx` - -Uses `ImageResponse` from `next/og` to generate a branded OG banner at build time. Reads the existing `app/icon.png` as a base64 data URI and renders it with the DeckyVault name and tagline on the brand background color. - -- [ ] **Step 1: Create `app/opengraph-image.tsx`** - -```tsx -import { ImageResponse } from "next/og" -import { readFile } from "node:fs/promises" -import { join } from "node:path" - -export const alt = "DeckyVault - Steam Deck Benchmarks & Settings" -export const size = { - width: 1200, - height: 630, -} -export const contentType = "image/png" - -export default async function Image() { - const logoData = await readFile( - join(process.cwd(), "app/icon.png"), - "base64" - ) - const logoSrc = `data:image/png;base64,${logoData}` - - return new ImageResponse( - ( -
- DeckyVault -
- DeckyVault -
-
- Steam Deck Benchmarks & Settings -
-
- ), - { - ...size, - } - ) -} -``` - -- [ ] **Step 2: Verify OG image builds** - -Run: `bun run build` -Expected: Build succeeds. `opengraph-image` route appears in build output. - ---- - -### Task 3: Create Twitter Card Image - -**Files:** -- Create: `app/twitter-image.tsx` - -Shares the same design as the OG image. Next.js supports separate `twitter-image` files. For now, we duplicate the OG image logic to allow independent customization later. - -- [ ] **Step 1: Create `app/twitter-image.tsx`** - -```tsx -import { ImageResponse } from "next/og" -import { readFile } from "node:fs/promises" -import { join } from "node:path" - -export const alt = "DeckyVault - Steam Deck Benchmarks & Settings" -export const size = { - width: 1200, - height: 630, -} -export const contentType = "image/png" - -export default async function Image() { - const logoData = await readFile( - join(process.cwd(), "app/icon.png"), - "base64" - ) - const logoSrc = `data:image/png;base64,${logoData}` - - return new ImageResponse( - ( -
- DeckyVault -
- DeckyVault -
-
- Steam Deck Benchmarks & Settings -
-
- ), - { - ...size, - } - ) -} -``` - -- [ ] **Step 2: Verify Twitter image builds** - -Run: `bun run build` -Expected: Build succeeds. Both `opengraph-image` and `twitter-image` routes appear. - ---- - -## Chunk 2: Sitemap, Robots.txt, and Manifest - -### Task 4: Create Dynamic Sitemap - -**Files:** -- Create: `app/sitemap.ts` - -Programmatically generates `sitemap.xml`. Currently static (just the homepage), but structured so that dynamic game pages can be added later by querying the database. - -- [ ] **Step 1: Create `app/sitemap.ts`** - -```ts -import type { MetadataRoute } from "next" - -const BASE_URL = "https://deckyvault.xyz" - -export default function sitemap(): MetadataRoute.Sitemap { - return [ - { - url: BASE_URL, - lastModified: new Date(), - changeFrequency: "weekly", - priority: 1, - }, - // Future dynamic routes can be added here: - // { - // url: `${BASE_URL}/games/${game.slug}`, - // lastModified: game.updatedAt, - // changeFrequency: "weekly", - // priority: 0.8, - // }, - ] -} -``` - -- [ ] **Step 2: Verify sitemap builds** - -Run: `bun run build` -Expected: Build succeeds. `/sitemap.xml` route available. - ---- - -### Task 5: Create Robots.txt - -**Files:** -- Create: `app/robots.ts` - -Allows all crawlers, points to the sitemap, and sets the host. - -- [ ] **Step 1: Create `app/robots.ts`** - -```ts -import type { MetadataRoute } from "next" - -export default function robots(): MetadataRoute.Robots { - return { - rules: { - userAgent: "*", - allow: "/", - }, - sitemap: "https://deckyvault.xyz/sitemap.xml", - host: "https://deckyvault.xyz", - } -} -``` - -- [ ] **Step 2: Verify robots.txt builds** - -Run: `bun run build` -Expected: Build succeeds. `/robots.txt` route available. - ---- - -### Task 6: Create Web App Manifest - -**Files:** -- Create: `app/manifest.ts` - -Generates `manifest.webmanifest` for PWA support and better mobile experience. Uses the existing `app/icon.png` and the brand colors from `globals.css`. - -- [ ] **Step 1: Create `app/manifest.ts`** - -```ts -import type { MetadataRoute } from "next" - -export default function manifest(): MetadataRoute.Manifest { - return { - name: "DeckyVault - Steam Deck Benchmarks & Settings", - short_name: "DeckyVault", - description: - "A fast, modern browser for finding game benchmarks, settings, and guides for Steam Deck OLED, Steam Deck LCD, and Steam Machine.", - start_url: "/", - display: "standalone", - background_color: "#100b14", - theme_color: "#eb3779", - icons: [ - { - src: "/icon.png", - sizes: "any", - type: "image/png", - }, - ], - } -} -``` - -Note: `app/icon.png` is served at `/icon.png` by Next.js (the existing build output confirms this route exists). - -- [ ] **Step 2: Verify manifest builds** - -Run: `bun run build` -Expected: Build succeeds. `/manifest.webmanifest` route available. - ---- - -## Chunk 3: README Update - -### Task 7: Update README.md - -**Files:** -- Modify: `README.md` - -The current README is outdated — references npm/yarn/pnpm instead of bun, doesn't mention Elysia, Drizzle, Tiptap, better-auth, S3 storage, or the actual full tech stack. Needs to reflect the current state and note that the site is under development. - -- [ ] **Step 1: Replace `README.md` content** - -```markdown -# DeckyVault - -An open-source, community-driven database for Steam Deck (OLED & LCD) and Steam Machine compatibility, performance metrics, and settings. - -> **Note:** DeckyVault is currently under active development. The site is live at [deckyvault.xyz](https://deckyvault.xyz) but features are being built incrementally. Stay tuned! - -## Why DeckyVault? - -Gaming on the Steam Deck is incredible, but finding reliable, detailed answers about whether a game will run well — or how to optimize it — can be frustrating. Existing resources are often fragmented, outdated, or lack the depth the community needs. - -**DeckyVault** aims to solve this by being the definitive, community-first hub for Steam Deck performance data. - -## Features - -- **Community-First Contributions** — Built by the community, for the community. Anyone can contribute data and improvements. -- **Streamlined Filters & Searching** — Quickly find the games you're looking for with powerful search and filtering. -- **Rich Performance Metrics** - - Community average FPS (**low**, **avg**, **high**). - - Extended FPS metrics for technologies like **FSR** and **Frame Generation**. -- **Beautifully Formatted Settings** — No more endless single-column lists. Every game features **sectioned setting tables** that mirror in-game menus. -- **Historical FPS Graphs** — Track performance improvements over time across: - - Game Versions - - Proton / Native builds - - SteamOS Versions -- **Multi-Device Support** — Dedicated metrics and optimized settings for **Steam Deck OLED**, **Steam Deck LCD**, and **Steam Machine**, all on a single page with quick toggles. -- **Comprehensive Compatibility Tracking** - - Proton vs. Native status - - Online Play & Anti-Cheat status - - Straightforward "Playable" tags -- **Loading & Storage Metrics** — Track initial launch times, game load times, world loading, and **SD Card** performance. -- **Community & Steam Integration** — On-site comments plus existing reviews, artwork, and stats pulled directly from Steam. -- **Open Source** — Fully transparent. Help us build the best resource for Deck gamers. - -## Tech Stack - -- **[Next.js 16](https://nextjs.org)** — React framework (App Router) -- **[React 19](https://react.dev)** — UI library -- **[TypeScript](https://www.typescriptlang.org)** — Type safety -- **[Tailwind CSS v4](https://tailwindcss.com)** — Utility-first styling -- **[Elysia](https://elysiajs.com)** — Backend API framework -- **[Drizzle ORM](https://orm.drizzle.team)** — Type-safe database queries -- **[PostgreSQL](https://www.postgresql.org)** — Primary database -- **[better-auth](https://better-auth.com)** — Authentication (Google & Discord OAuth) -- **[Tiptap](https://tiptap.dev)** — Rich text editor -- **[Motion](https://motion.dev)** — Animations -- **[AWS S3](https://aws.amazon.com/s3/)** — File storage -- **[Bun](https://bun.sh)** — Package manager & runtime - -## Getting Started - -Clone the repository and install dependencies: - -```bash -bun install -``` - -Copy the environment file and configure your values: - -```bash -cp .env.example .env.local -``` - -Run the development server: - -```bash -bun run dev -``` - -Open [http://localhost:3000](http://localhost:3000) to see the result. - -## Scripts - -| Command | Description | -|---------|-------------| -| `bun run dev` | Start development server (HTTPS) | -| `bun run build` | Create production build | -| `bun run start` | Start production server | -| `bun run lint` | Run ESLint | - -## Contributing - -We welcome contributions of all kinds! Whether you want to: - -- Suggest a new feature -- Report a bug -- Improve the design -- Contribute code - -Feel free to open an [issue](https://github.com/AdrianBonpin/deckyvault/issues) or submit a [pull request](https://github.com/AdrianBonpin/deckyvault/pulls). - -If you're interested in helping develop or design the project, don't hesitate to reach out! - -## Roadmap - -This project is actively being developed. Here are some of the major items on our radar: - -- [ ] Core database and API for game entries -- [ ] Advanced search and filter system -- [ ] Community submission and moderation tools -- [ ] FPS graphing and historical data visualization -- [ ] Steam API integration for reviews, artwork, and stats -- [ ] Support for additional handheld devices *(future consideration)* - -Stay tuned for updates! - -## License - -This project is open-source. *(Add your specific license here, e.g., MIT, GPL-3.0)* - -## Acknowledgements - -- Built with love for the Steam Deck community. -- Check out the author's existing project for an idea of what to expect: **[https://grounds.ph](https://grounds.ph)** - ---- - -*DeckyVault is an independent project and is not affiliated with Valve Corporation.* -``` - -- [ ] **Step 2: Verify no build impact** - -Run: `bun run build` -Expected: Build succeeds (README changes don't affect build). - ---- - -## Chunk 4: Verification & Lint/Build Cleanup - -### Task 8: Final Verification - -**Files:** -- All files created/modified in Tasks 1-7 - -- [ ] **Step 1: Run full lint** - -Run: `bun run lint` -Expected: No errors or warnings. - -- [ ] **Step 2: Run full build** - -Run: `bun run build` -Expected: Build succeeds. Verify the following routes are generated: -- `/` — main page -- `/icon.png` — favicon (already existed) -- `/opengraph-image` — OG banner -- `/twitter-image` — Twitter card -- `/sitemap.xml` — sitemap -- `/robots.txt` — robots -- `/manifest.webmanifest` — PWA manifest - -- [ ] **Step 3: Fix any lint or build errors** - -If lint or build fails, fix the issues before claiming completion. Common issues to watch for: -- TypeScript errors in OG image files (ensure `readFile` import works) -- ESLint warnings on unused variables or missing dependencies -- Build failures from incorrect `ImageResponse` JSX - -- [ ] **Step 4: Commit all changes** - -```bash -git add app/layout.tsx app/opengraph-image.tsx app/twitter-image.tsx app/sitemap.ts app/robots.ts app/manifest.ts README.md -git commit -m "feat: add comprehensive SEO (metadata, OG images, sitemap, robots, manifest) and update README" -``` - ---- - -## Summary of Routes After Implementation - -| Route | Type | Source File | -|-------|------|-------------| -| `/` | Static page | `app/page.tsx` | -| `/icon.png` | Favicon | `app/icon.png` | -| `/opengraph-image` | OG banner (PNG) | `app/opengraph-image.tsx` | -| `/twitter-image` | Twitter card (PNG) | `app/twitter-image.tsx` | -| `/sitemap.xml` | Sitemap | `app/sitemap.ts` | -| `/robots.txt` | Robots | `app/robots.ts` | -| `/manifest.webmanifest` | PWA manifest | `app/manifest.ts` | diff --git a/lib/auth-client.ts b/lib/auth-client.ts new file mode 100644 index 0000000..613ab7c --- /dev/null +++ b/lib/auth-client.ts @@ -0,0 +1,15 @@ +import { createAuthClient } from 'better-auth/client' +import { adminClient, emailOTPClient, lastLoginMethodClient } from 'better-auth/client/plugins' +import { passkeyClient } from '@better-auth/passkey/client' + +export const authClient = createAuthClient({ + baseURL: process.env.NEXT_PUBLIC_BASE_URL || "https://localhost:3000", + plugins: [ + emailOTPClient(), + passkeyClient(), + lastLoginMethodClient(), + adminClient() + ] +}) + +export const { signIn, signOut, signUp, getSession, useSession } = authClient \ No newline at end of file diff --git a/lib/auth.ts b/lib/auth.ts new file mode 100644 index 0000000..03a1784 --- /dev/null +++ b/lib/auth.ts @@ -0,0 +1,59 @@ +import { betterAuth } from 'better-auth' +import { admin, emailOTP, lastLoginMethod } from 'better-auth/plugins' +import { passkey } from '@better-auth/passkey' +import { drizzleAdapter } from '@better-auth/drizzle-adapter' +import { db } from '@/lib/db/index' + +export const auth = betterAuth({ + experimental: { joins: true }, + database: drizzleAdapter(db, { + provider: 'pg' + }), + plugins: [ + emailOTP({ + async sendVerificationOTP({ email, otp, type }) { + if (type === 'sign-in') { + // TODO: send the OTP to the user's email address + } else if (type === 'email-verification') { + // TODO: send the OTP to the user's email address for email verification + } else { + // TODO: send the OTP to the user's email address for password reset + } + } + }), + passkey(), + lastLoginMethod(), + admin() + ], + socialProviders: { + google: { + clientId: process.env.GOOGLE_CLIENT_ID || "", + clientSecret: process.env.GOOGLE_CLIENT_SECRET || "", + }, + discord: { + clientId: process.env.DISCORD_CLIENT_ID || "", + clientSecret: process.env.DISCORD_CLIENT_SECRET || "", + }, + }, + trustedOrigins: [process.env.NEXT_PUBLIC_BASE_URL || "https://localhost:3000"], + rateLimit: { + enabled: true, + window: 60, + max: 100 + }, + session: { + expiresIn: 60 * 60 * 24 * 7, + updateAge: 60 * 60 * 24, + cookieCache: { + enabled: true, + maxAge: 15 * 60, + }, + }, + account: { + accountLinking: { + enabled: true, + trustedProviders: ['google', 'discord'], + allowDifferentEmails: true + } + } +}) \ No newline at end of file