Skip to content

Commit d090373

Browse files
Fix homepage.js_rendered: Critical data ('0 coupons', '0 stores') rendered client-side with no SSR fallback (#12)
Co-authored-by: crawlproof[bot] <286981042+crawlproof[bot]@users.noreply.github.com>
1 parent eaec1d8 commit d090373

1 file changed

Lines changed: 70 additions & 5 deletions

File tree

apps/web/app/page.tsx

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export const dynamic = 'force-dynamic';
22

3+
import type { Metadata } from 'next';
34
import Link from 'next/link';
45
import CouponCard from '@/components/CouponCard';
56
import 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+
1417
async 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+
57115
export 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

Comments
 (0)