feat: add comprehensive SEO (metadata, OG images, sitemap, robots, manifest) and update README
@@ -0,0 +1,56 @@
|
||||
# =============================================================================
|
||||
# Environment Configuration
|
||||
# =============================================================================
|
||||
# Copy this file to .env.local and fill in your actual values
|
||||
# Never commit .env.local to version control
|
||||
# =============================================================================
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# SYSTEM & DEPLOYMENT
|
||||
# -----------------------------------------------------------------------------
|
||||
NIXPACKS_NODE_VERSION=22
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# DATABASE
|
||||
# -----------------------------------------------------------------------------
|
||||
# PostgreSQL connection string
|
||||
DATABASE_URL="postgresql://user:password@localhost:5432/grounds"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# APPLICATION URLS
|
||||
# -----------------------------------------------------------------------------
|
||||
# Base URL of the application (client-accessible)
|
||||
NEXT_PUBLIC_SITE_URL="https://localhost:3000"
|
||||
|
||||
# Auth callback URL
|
||||
NEXT_PUBLIC_APP_URL="https://localhost:3000"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# BETTER AUTH
|
||||
# -----------------------------------------------------------------------------
|
||||
# Better Auth configuration
|
||||
# URL must match your app URL (with protocol)
|
||||
BETTER_AUTH_URL="https://localhost:3000"
|
||||
|
||||
# Secret key for signing tokens (generate with: openssl rand -base64 32)
|
||||
BETTER_AUTH_SECRET="your-secret-key-here"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# AUTHENTICATION
|
||||
# -----------------------------------------------------------------------------
|
||||
# Google OAuth credentials (required)
|
||||
GOOGLE_CLIENT_ID=""
|
||||
GOOGLE_CLIENT_SECRET=""
|
||||
|
||||
# Discord OAuth credentials (required)
|
||||
DISCORD_CLIENT_ID=""
|
||||
DISCORD_CLIENT_SECRET=""
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# STORAGE (S3 COMPATIBLE)
|
||||
# -----------------------------------------------------------------------------
|
||||
# S3-compatible credentials for file storage
|
||||
S3_ACCESS_KEY_ID=""
|
||||
S3_SECRET_ACCESS_KEY=""
|
||||
S3_BUCKET_NAME=""
|
||||
S3_PUBLIC_URL=""
|
||||
@@ -32,6 +32,7 @@ yarn-error.log*
|
||||
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env*
|
||||
!.env.example
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
An open-source, community-driven database for Steam Deck (OLED & LCD) and Steam Machine compatibility, performance metrics, and settings.
|
||||
|
||||
[//]: # "Add shields here if available: e.g., license, contributors, last commit"
|
||||
> **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?
|
||||
|
||||
@@ -33,36 +33,49 @@ Gaming on the Steam Deck is incredible, but finding reliable, detailed answers a
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- **[Next.js](https://nextjs.org)** — React framework for production
|
||||
- **[React](https://react.dev)** — UI library
|
||||
- **[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](https://tailwindcss.com)** — Utility-first styling *(verify in your local setup)*
|
||||
- **[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
|
||||
|
||||
First, clone the repository and install dependencies:
|
||||
Clone the repository and install dependencies:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
# or
|
||||
yarn install
|
||||
# or
|
||||
pnpm install
|
||||
bun install
|
||||
```
|
||||
|
||||
Then, run the development server:
|
||||
Copy the environment file and configure your values:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
cp .env.example .env.local
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
Run the development server:
|
||||
|
||||
You can start editing the pages by modifying files in the `app/` directory. The page auto-updates as you edit the file.
|
||||
```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
|
||||
|
||||
@@ -73,7 +86,7 @@ We welcome contributions of all kinds! Whether you want to:
|
||||
- Improve the design
|
||||
- Contribute code
|
||||
|
||||
Feel free to open an [issue](../../issues) or submit a [pull request](../../pulls).
|
||||
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!
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 25 KiB |
@@ -1,26 +1,17 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--font-sans: var(--font-geist-sans);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
}
|
||||
/* Colors */
|
||||
--color-background: #100b14;
|
||||
--color-foreground: #180161;
|
||||
--color-primary: #eb3779;
|
||||
--color-secondary: #571b8b;
|
||||
--color-accent: #fb793c;
|
||||
--color-text: #ebe4f1;
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: #0a0a0a;
|
||||
--foreground: #ededed;
|
||||
}
|
||||
}
|
||||
/* Defaults */
|
||||
--default-transition-duration: 0.3s;
|
||||
|
||||
body {
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
/* Fonts */
|
||||
--font-sans: var(--font-lexend);
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 93 KiB |
@@ -1,33 +1,93 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import type { Metadata } from "next"
|
||||
import { Lexend } from "next/font/google"
|
||||
import "./globals.css"
|
||||
import Script from "next/script"
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
const font = Lexend({
|
||||
variable: "--font-lexend",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: "--font-geist-mono",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
})
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
};
|
||||
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;
|
||||
children: React.ReactNode
|
||||
}>) {
|
||||
return (
|
||||
<html
|
||||
lang="en"
|
||||
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
|
||||
lang='en'
|
||||
className={`${font.variable} bg-background text-text antialiased overscroll-none`}
|
||||
>
|
||||
<body className="min-h-full flex flex-col">{children}</body>
|
||||
<body className='min-h-full flex flex-col relative'>
|
||||
{children}
|
||||
<Script
|
||||
src='https://analytics.ranlabs.space/api/script.js'
|
||||
data-site-id='b9817e8df599'
|
||||
strategy='afterInteractive'
|
||||
/>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
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",
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
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(
|
||||
(
|
||||
<div
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
background: "#100b14",
|
||||
color: "#ebe4f1",
|
||||
fontFamily: "sans-serif",
|
||||
gap: "16px",
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={logoSrc}
|
||||
alt="DeckyVault"
|
||||
height={120}
|
||||
style={{ borderRadius: "16px" }}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
fontSize: 64,
|
||||
fontWeight: 700,
|
||||
letterSpacing: "-0.02em",
|
||||
}}
|
||||
>
|
||||
DeckyVault
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: 28,
|
||||
fontWeight: 400,
|
||||
opacity: 0.8,
|
||||
}}
|
||||
>
|
||||
Steam Deck Benchmarks & Settings
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
{
|
||||
...size,
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1,65 +1,87 @@
|
||||
import Image from "next/image";
|
||||
"use client"
|
||||
|
||||
import { AnimatePresence, motion } from "motion/react"
|
||||
import { useState } from "react"
|
||||
import logo from "@/app/icon.png"
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
|
||||
export default function Landing() {
|
||||
const words = ["benchmarks", "settings", "guides", "reviews"]
|
||||
|
||||
const [currentWord, setCurrentWord] = useState(0)
|
||||
|
||||
setTimeout(() => {
|
||||
setCurrentWord((currentWord + 1) % words.length)
|
||||
}, 2000)
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="flex flex-col flex-1 items-center justify-center bg-zinc-50 font-sans dark:bg-black">
|
||||
<main className="flex flex-1 w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/next.svg"
|
||||
alt="Next.js logo"
|
||||
width={100}
|
||||
height={20}
|
||||
priority
|
||||
/>
|
||||
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
|
||||
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
|
||||
To get started, edit the page.tsx file.
|
||||
</h1>
|
||||
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
|
||||
Looking for a starting point or more instructions? Head over to{" "}
|
||||
<a
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
||||
>
|
||||
Templates
|
||||
</a>{" "}
|
||||
or the{" "}
|
||||
<a
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
||||
>
|
||||
Learning
|
||||
</a>{" "}
|
||||
center.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
|
||||
<a
|
||||
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
<section
|
||||
id='hero'
|
||||
className='w-dvw h-dvh flex flex-col items-center justify-center relative'
|
||||
>
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={16}
|
||||
height={16}
|
||||
src={logo}
|
||||
alt=''
|
||||
className='h-30 w-auto'
|
||||
loading='eager'
|
||||
/>
|
||||
Deploy Now
|
||||
</a>
|
||||
<a
|
||||
className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
<motion.h1
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
className='font-bold text-5xl text-center'
|
||||
>
|
||||
Documentation
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
DeckyVault
|
||||
</motion.h1>
|
||||
<motion.h2
|
||||
initial={{
|
||||
opacity: 0,
|
||||
}}
|
||||
animate={{ opacity: 1, transition: { delay: 0.5 } }}
|
||||
className='mt-4 flex flex-col items-center font-semibold text-2xl'
|
||||
>
|
||||
<motion.span
|
||||
key='intro'
|
||||
className='text-center'
|
||||
>
|
||||
A fast, modern browser for finding game
|
||||
</motion.span>
|
||||
<AnimatePresence
|
||||
mode='wait'
|
||||
initial={false}
|
||||
>
|
||||
<motion.span
|
||||
key={currentWord}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className='text-primary font-bold'
|
||||
>
|
||||
{words[currentWord]}
|
||||
</motion.span>
|
||||
</AnimatePresence>
|
||||
</motion.h2>
|
||||
<motion.p
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 0.6, transition: { delay: 1 } }}
|
||||
className='mt-4 text-center text-lg max-w-xl'
|
||||
>
|
||||
Stay tuned for the launch of DeckyVault
|
||||
</motion.p>
|
||||
<motion.small
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1, transition: { delay: 1.5 } }}
|
||||
className='absolute bottom-4 text-center text-xs flex flex-row gap-1'
|
||||
>
|
||||
<span className='opacity-60'>2026 DeckyVault.</span>
|
||||
<Link
|
||||
title='Visit our Github Repository'
|
||||
href='https://github.com/AdrianBonpin/deckyvault'
|
||||
className='text-accent opacity-60 hover:opacity-100 transition-opacity'
|
||||
>
|
||||
Github.
|
||||
</Link>
|
||||
</motion.small>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
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",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
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,
|
||||
// },
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
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(
|
||||
(
|
||||
<div
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
background: "#100b14",
|
||||
color: "#ebe4f1",
|
||||
fontFamily: "sans-serif",
|
||||
gap: "16px",
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={logoSrc}
|
||||
alt="DeckyVault"
|
||||
height={120}
|
||||
style={{ borderRadius: "16px" }}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
fontSize: 64,
|
||||
fontWeight: 700,
|
||||
letterSpacing: "-0.02em",
|
||||
}}
|
||||
>
|
||||
DeckyVault
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: 28,
|
||||
fontWeight: 400,
|
||||
opacity: 0.8,
|
||||
}}
|
||||
>
|
||||
Steam Deck Benchmarks & Settings
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
{
|
||||
...size,
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -15,6 +15,7 @@
|
||||
"dotenv": "^17.4.2",
|
||||
"drizzle-orm": "^0.45.2",
|
||||
"elysia": "^1.4.28",
|
||||
"lucide-react": "^1.11.0",
|
||||
"motion": "^12.38.0",
|
||||
"next": "16.2.4",
|
||||
"pg": "^8.20.0",
|
||||
@@ -1042,6 +1043,8 @@
|
||||
|
||||
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
||||
|
||||
"lucide-react": ["lucide-react@1.11.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-UOhjdztXCgdBReRcIhsvz2siIBogfv/lhJEIViCpLt924dO+GDms9T7DNoucI23s6kEPpe988m5N0D2ajnzb2g=="],
|
||||
|
||||
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
||||
|
||||
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
|
||||
|
||||
@@ -0,0 +1,616 @@
|
||||
# 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 (
|
||||
<html
|
||||
lang='en'
|
||||
className={`${font.variable} bg-background text-text antialiased overscroll-none`}
|
||||
>
|
||||
<body className='min-h-full flex flex-col relative'>
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **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(
|
||||
(
|
||||
<div
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
background: "#100b14",
|
||||
color: "#ebe4f1",
|
||||
fontFamily: "sans-serif",
|
||||
gap: "16px",
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={logoSrc}
|
||||
alt="DeckyVault"
|
||||
height="120"
|
||||
style={{ borderRadius: "16px" }}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
fontSize: 64,
|
||||
fontWeight: 700,
|
||||
letterSpacing: "-0.02em",
|
||||
}}
|
||||
>
|
||||
DeckyVault
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: 28,
|
||||
fontWeight: 400,
|
||||
opacity: 0.8,
|
||||
}}
|
||||
>
|
||||
Steam Deck Benchmarks & Settings
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
{
|
||||
...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(
|
||||
(
|
||||
<div
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
background: "#100b14",
|
||||
color: "#ebe4f1",
|
||||
fontFamily: "sans-serif",
|
||||
gap: "16px",
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={logoSrc}
|
||||
alt="DeckyVault"
|
||||
height="120"
|
||||
style={{ borderRadius: "16px" }}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
fontSize: 64,
|
||||
fontWeight: 700,
|
||||
letterSpacing: "-0.02em",
|
||||
}}
|
||||
>
|
||||
DeckyVault
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: 28,
|
||||
fontWeight: 400,
|
||||
opacity: 0.8,
|
||||
}}
|
||||
>
|
||||
Steam Deck Benchmarks & Settings
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
{
|
||||
...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` |
|
||||
@@ -3,7 +3,7 @@
|
||||
"version": "2026.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"dev": "next dev --experimental-https",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "eslint"
|
||||
@@ -19,6 +19,7 @@
|
||||
"dotenv": "^17.4.2",
|
||||
"drizzle-orm": "^0.45.2",
|
||||
"elysia": "^1.4.28",
|
||||
"lucide-react": "^1.11.0",
|
||||
"motion": "^12.38.0",
|
||||
"next": "16.2.4",
|
||||
"pg": "^8.20.0",
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
||||
|
Before Width: | Height: | Size: 391 B |
@@ -1 +0,0 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
||||
|
Before Width: | Height: | Size: 1.0 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB |
@@ -1 +0,0 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
||||
|
Before Width: | Height: | Size: 128 B |
@@ -1 +0,0 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
||||
|
Before Width: | Height: | Size: 385 B |