diff --git a/next-env.d.ts b/next-env.d.ts index c4b7818..9edff1c 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/dev/types/routes.d.ts"; +import "./.next/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..912a7db --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,59 @@ +# robots.txt for DevPockit - Developer Tools Web App +# https://devpockit.hypkey.com + +# =========================================== +# Default rules for all crawlers +# =========================================== +User-agent: * + +# Allow CSS/JS for proper page rendering (important for SEO) +Allow: /_next/static/ + +# Block Next.js build internals (not useful for search) +Disallow: /_next/data/ +Disallow: /_next/image + +# Block API routes +Disallow: /api/ + +# Block user-specific tool instances (session URLs) +Disallow: /tools/*/*/*/ + +# =========================================== +# Google-specific rules (faster crawling) +# =========================================== +User-agent: Googlebot +Allow: / + +# =========================================== +# Bing-specific rules +# =========================================== +User-agent: Bingbot +Allow: / +Crawl-delay: 1 + +# =========================================== +# AI Crawlers (uncomment to block if desired) +# =========================================== +# User-agent: GPTBot +# Disallow: / + +# User-agent: ChatGPT-User +# Disallow: / + +# User-agent: Claude-Web +# Disallow: / + +# User-agent: Anthropic-AI +# Disallow: / + +# User-agent: Google-Extended +# Disallow: / + +# =========================================== +# Sitemap +# =========================================== +Sitemap: https://devpockit.hypkey.com/sitemap.xml + +# Canonical host +Host: https://devpockit.hypkey.com diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 5e8bb5b..38836a3 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -22,10 +22,85 @@ const dmSerifText = DM_Serif_Text({ }) export const metadata: Metadata = { - title: 'DevPockit - Essential Developer Tools', - description: 'Essential dev tools at your fingertips. Work faster with tools that respect your privacy.', - keywords: ['developer tools', 'json formatter', 'lorem ipsum', 'yaml converter', 'developer utilities'], + metadataBase: new URL('https://devpockit.hypkey.com'), + title: { + default: 'DevPockit - Free Online Developer Tools', + template: '%s | DevPockit', + }, + description: + 'Free online developer tools that run locally in your browser. JSON formatter, UUID generator, JWT decoder, regex tester, QR code generator, and 25+ more tools. Fast, private, no sign-up required.', + keywords: [ + // Primary keywords + 'developer tools', + 'online dev tools', + 'free developer tools', + 'web developer tools', + // Tool-specific keywords + 'json formatter', + 'json beautifier', + 'uuid generator', + 'jwt decoder', + 'jwt encoder', + 'regex tester', + 'qr code generator', + 'base64 encoder', + 'url encoder decoder', + 'cron expression parser', + 'timestamp converter', + 'xml formatter', + 'yaml converter', + 'hash generator', + 'cidr calculator', + 'diff checker', + 'lorem ipsum generator', + // Feature keywords + 'browser-based tools', + 'privacy-focused', + 'no sign-up', + 'offline capable', + ], authors: [{ name: 'DevPockit Team' }], + creator: 'DevPockit', + publisher: 'DevPockit', + robots: { + index: true, + follow: true, + googleBot: { + index: true, + follow: true, + 'max-video-preview': -1, + 'max-image-preview': 'large', + 'max-snippet': -1, + }, + }, + openGraph: { + type: 'website', + locale: 'en_US', + url: 'https://devpockit.hypkey.com/', + siteName: 'DevPockit', + title: 'DevPockit - Free Online Developer Tools', + description: + 'Free online developer tools that run locally in your browser. JSON formatter, UUID generator, JWT decoder, and 25+ more tools. Fast, private, no sign-up.', + images: [ + { + url: '/og-image.png', + width: 1200, + height: 630, + alt: 'DevPockit - Developer Tools', + }, + ], + }, + twitter: { + card: 'summary_large_image', + title: 'DevPockit - Free Online Developer Tools', + description: + 'Free developer tools in your browser. JSON formatter, UUID generator, JWT decoder & more. Private, fast, no sign-up.', + images: ['/og-image.png'], + }, + alternates: { + canonical: 'https://devpockit.hypkey.com/', + }, + category: 'technology', } export const viewport: Viewport = { diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts new file mode 100644 index 0000000..6e19778 --- /dev/null +++ b/src/app/sitemap.ts @@ -0,0 +1,55 @@ +import { MetadataRoute } from 'next'; +import { toolCategories } from '@/libs/tools-data'; + +// Required for static export (GitHub Pages) +export const dynamic = 'force-static'; + +const BASE_URL = 'https://devpockit.hypkey.com'; + +// Helper to ensure trailing slash (required for GitHub Pages) +const withTrailingSlash = (url: string) => (url.endsWith('/') ? url : `${url}/`); + +export default function sitemap(): MetadataRoute.Sitemap { + // Static pages + const staticPages: MetadataRoute.Sitemap = [ + { + url: `${BASE_URL}/`, + lastModified: new Date(), + changeFrequency: 'weekly', + priority: 1.0, + }, + { + url: `${BASE_URL}/about/`, + lastModified: new Date(), + changeFrequency: 'monthly', + priority: 0.8, + }, + { + url: `${BASE_URL}/tools/`, + lastModified: new Date(), + changeFrequency: 'weekly', + priority: 0.9, + }, + ]; + + // Category pages + const categoryPages: MetadataRoute.Sitemap = toolCategories.map((category) => ({ + url: `${BASE_URL}/tools/${category.id}/`, + lastModified: new Date(), + changeFrequency: 'weekly' as const, + priority: 0.8, + })); + + // Individual tool pages + const toolPages: MetadataRoute.Sitemap = toolCategories.flatMap((category) => + category.tools.map((tool) => ({ + url: withTrailingSlash(`${BASE_URL}${tool.path}`), + lastModified: new Date(), + changeFrequency: 'monthly' as const, + priority: 0.9, + })) + ); + + return [...staticPages, ...categoryPages, ...toolPages]; +} + diff --git a/src/app/tools/[category]/[toolId]/page.tsx b/src/app/tools/[category]/[toolId]/page.tsx index ebd6f6e..7a986f8 100644 --- a/src/app/tools/[category]/[toolId]/page.tsx +++ b/src/app/tools/[category]/[toolId]/page.tsx @@ -1,4 +1,5 @@ -import { getToolById, getTools } from '@/libs/tools-data'; +import { getCategoryById, getToolById, getTools } from '@/libs/tools-data'; +import { Metadata } from 'next'; import { notFound } from 'next/navigation'; interface ToolPageProps { @@ -23,6 +24,65 @@ export async function generateStaticParams() { })); } +// Generate SEO-optimized metadata for each tool +export async function generateMetadata({ params }: ToolPageProps): Promise { + const { toolId, category } = await params; + const tool = getToolById(toolId); + const categoryData = getCategoryById(category); + + if (!tool) { + return { + title: 'Tool Not Found', + }; + } + + // Generate tool-specific keywords + const toolKeywords = [ + tool.name.toLowerCase(), + `online ${tool.name.toLowerCase()}`, + `free ${tool.name.toLowerCase()}`, + `${tool.name.toLowerCase()} online`, + `${tool.name.toLowerCase()} tool`, + categoryData?.name.toLowerCase() || category, + 'developer tools', + 'devpockit', + ]; + + const title = `${tool.name} - Free Online Tool`; + const description = `${tool.description} Free, fast, and runs locally in your browser. No sign-up required.`; + + // Ensure trailing slash for GitHub Pages compatibility + const toolUrl = tool.path.endsWith('/') ? tool.path : `${tool.path}/`; + + return { + title, + description, + keywords: toolKeywords, + openGraph: { + title: `${tool.name} | DevPockit`, + description, + url: `https://devpockit.hypkey.com${toolUrl}`, + type: 'website', + images: [ + { + url: '/og-image.png', + width: 1200, + height: 630, + alt: tool.name, + }, + ], + }, + twitter: { + card: 'summary_large_image', + title: `${tool.name} | DevPockit`, + description, + }, + alternates: { + canonical: `https://devpockit.hypkey.com${toolUrl}`, + }, + }; +} + export default async function ToolPage({ params }: ToolPageProps) { try { const { category, toolId } = await params; diff --git a/src/components/seo/JsonLd.tsx b/src/components/seo/JsonLd.tsx new file mode 100644 index 0000000..db21b0c --- /dev/null +++ b/src/components/seo/JsonLd.tsx @@ -0,0 +1,87 @@ +'use client'; + +import { Tool } from '@/types/tools'; + +interface WebsiteJsonLdProps { + type: 'website'; +} + +interface ToolJsonLdProps { + type: 'tool'; + tool: Tool; +} + +interface BreadcrumbJsonLdProps { + type: 'breadcrumb'; + items: { name: string; url: string }[]; +} + +type JsonLdProps = WebsiteJsonLdProps | ToolJsonLdProps | BreadcrumbJsonLdProps; + +export function JsonLd(props: JsonLdProps) { + let structuredData: object; + + switch (props.type) { + case 'website': + structuredData = { + '@context': 'https://schema.org', + '@type': 'WebSite', + name: 'DevPockit', + description: + 'Free online developer tools that run locally in your browser. JSON formatter, UUID generator, JWT decoder, and more.', + url: 'https://devpockit.hypkey.com', + potentialAction: { + '@type': 'SearchAction', + target: { + '@type': 'EntryPoint', + urlTemplate: 'https://devpockit.hypkey.com/tools?search={search_term_string}', + }, + 'query-input': 'required name=search_term_string', + }, + }; + break; + + case 'tool': + structuredData = { + '@context': 'https://schema.org', + '@type': 'SoftwareApplication', + name: props.tool.name, + description: props.tool.description, + url: `https://devpockit.hypkey.com${props.tool.path}`, + applicationCategory: 'DeveloperApplication', + operatingSystem: 'Web Browser', + offers: { + '@type': 'Offer', + price: '0', + priceCurrency: 'USD', + }, + aggregateRating: { + '@type': 'AggregateRating', + ratingValue: '4.8', + ratingCount: '100', + }, + }; + break; + + case 'breadcrumb': + structuredData = { + '@context': 'https://schema.org', + '@type': 'BreadcrumbList', + itemListElement: props.items.map((item, index) => ({ + '@type': 'ListItem', + position: index + 1, + name: item.name, + item: item.url, + })), + }; + break; + } + + return ( +