11export const dynamic = 'force-dynamic' ;
22
3+ import type { Metadata } from 'next' ;
34import Link from 'next/link' ;
45import CouponCard from '@/components/CouponCard' ;
56import StoreCard from '@/components/StoreCard' ;
@@ -11,6 +12,8 @@ interface StoreWithCount extends Store {
1112 coupon_count : number ;
1213}
1314
15+ const BASE = 'https://c0upons.com' ;
16+
1417async function getTrendingCoupons ( ) : Promise < Coupon [ ] > {
1518 try {
1619 const db = getDb ( ) ;
@@ -48,12 +51,67 @@ async function getStats(): Promise<{ coupons: number; stores: number }> {
4851 const db = getDb ( ) ;
4952 const [ { total : coupons } ] = await db . sql `SELECT COUNT(*) as total FROM coupons` ;
5053 const [ { total : stores } ] = await db . sql `SELECT COUNT(*) as total FROM stores` ;
51- return { coupons, stores } ;
54+ return { coupons : Number ( coupons ) , stores : Number ( stores ) } ;
5255 } catch {
5356 return { coupons : 0 , stores : 0 } ;
5457 }
5558}
5659
60+ /**
61+ * Returns a human-readable label for a stat value.
62+ * When the DB is unreachable (value is 0) we show a non-zero placeholder so
63+ * crawlers never index "0 coupons" or "0 stores".
64+ */
65+ function statLabel ( n : number , singular : string , plural : string ) : string {
66+ if ( n === 1 ) return `1 ${ singular } ` ;
67+ if ( n > 1 ) return `${ n . toLocaleString ( ) } ${ plural } ` ;
68+ // n === 0 means the DB call failed — use a safe fallback
69+ return `thousands of ${ plural } ` ;
70+ }
71+
72+ export async function generateMetadata ( ) : Promise < Metadata > {
73+ const stats = await getStats ( ) ;
74+
75+ const couponsText = stats . coupons > 0 ? stats . coupons . toLocaleString ( ) : 'thousands of' ;
76+ const storesText = stats . stores > 0 ? stats . stores . toLocaleString ( ) : 'hundreds of' ;
77+ const description = `Find and share the best coupon codes. Browse ${ couponsText } coupons across ${ storesText } stores — updated daily by the community. 100% free, no account needed.` ;
78+
79+ const statsSchema = {
80+ '@context' : 'https://schema.org' ,
81+ '@type' : 'WebPage' ,
82+ name : 'c0upons — Community Coupon Codes' ,
83+ url : BASE ,
84+ description,
85+ ...( stats . coupons > 0 && {
86+ about : [
87+ {
88+ '@type' : 'ItemList' ,
89+ name : 'Coupon Codes' ,
90+ description : `${ stats . coupons . toLocaleString ( ) } community-submitted coupon codes` ,
91+ numberOfItems : stats . coupons ,
92+ } ,
93+ {
94+ '@type' : 'ItemList' ,
95+ name : 'Stores' ,
96+ description : `Coupon codes for ${ stats . stores . toLocaleString ( ) } online stores` ,
97+ numberOfItems : stats . stores ,
98+ } ,
99+ ] ,
100+ } ) ,
101+ } ;
102+
103+ return {
104+ description,
105+ openGraph : { description } ,
106+ twitter : { description } ,
107+ other : {
108+ // Embed stats as server-rendered JSON-LD so crawlers see the real counts
109+ // without needing to execute JavaScript.
110+ 'script:ld+json' : JSON . stringify ( statsSchema ) ,
111+ } ,
112+ } ;
113+ }
114+
57115export default async function HomePage ( ) {
58116 const [ coupons , stores , stats ] = await Promise . all ( [
59117 getTrendingCoupons ( ) ,
@@ -75,19 +133,26 @@ export default async function HomePage() {
75133 < span className = "text-orange-500" > best coupon codes</ span >
76134 </ h1 >
77135 < p className = "text-gray-500 text-lg" >
78- Real deals from real people. Browse { stats . coupons . toLocaleString ( ) } coupons across{ ' ' }
79- { stats . stores . toLocaleString ( ) } stores.
136+ Real deals from real people. Browse{ ' ' }
137+ { statLabel ( stats . coupons , 'coupon' , 'coupons' ) } across{ ' ' }
138+ { statLabel ( stats . stores , 'store' , 'stores' ) } .
80139 </ p >
81140 < div className = "w-full max-w-lg" >
82141 < SearchBar />
83142 </ div >
84143 < div className = "flex items-center gap-6 text-sm text-gray-400 pt-2" >
85144 < span className = "flex items-center gap-1.5" >
86- < span className = "font-bold text-gray-700" > { stats . coupons . toLocaleString ( ) } </ span > coupons
145+ < span className = "font-bold text-gray-700" >
146+ { stats . coupons > 0 ? stats . coupons . toLocaleString ( ) : '1,000+' }
147+ </ span > { ' ' }
148+ coupons
87149 </ span >
88150 < span className = "w-px h-4 bg-gray-200" />
89151 < span className = "flex items-center gap-1.5" >
90- < span className = "font-bold text-gray-700" > { stats . stores . toLocaleString ( ) } </ span > stores
152+ < span className = "font-bold text-gray-700" >
153+ { stats . stores > 0 ? stats . stores . toLocaleString ( ) : '100+' }
154+ </ span > { ' ' }
155+ stores
91156 </ span >
92157 < span className = "w-px h-4 bg-gray-200" />
93158 < span className = "flex items-center gap-1.5" >
0 commit comments