SPB Git forge

spb/trouve-ka

Public ★ Pinned

Trouve-KA — moteur de recherche web indépendant, Québec-first. Crawler distribué, index OpenSearch, ranking bilingue, galerie d'images. En prod : www.trouve-ka.com

40commits 1branches 0releases
9.2 MBsize
maindefault branch
23 days agolast push
Python 51.9% TypeScript 32.7% JavaScript 4.8% CSS 4.1% Shell 2.9% HTML 1.8% SQL 1.5%

SEO : canonical + JSON-LD (WebSite/SearchAction/Organization), robots.txt, sitemap.xml, title enrichi, lang fr-CA, noindex des résultats

- layout : metadataBase + alternates.canonical ./, title « moteur de recherche du web québécois » (template %s | Trouve-Ka), html lang=fr-CA, JSON-LD WebSite (SearchAction /search?q={search_term_string}) + Organization
- app/robots.ts : Allow /, Disallow /api/ /admin /search (espace de résultats infini), lien sitemap
- app/sitemap.ts : accueil + pages publiques statiques (trouveka-bot, status, stats, soumettre, contact)
- /search : robots noindex,follow sur les pages de résultats

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Simon-Pierre Boucher committed 1 mo ago (Aug 22, 2026) parent 058cc14

4 changed files +115 −6

modified apps/web/app/layout.tsx +48 −5
@@ -36,20 +36,25 @@ const fraunces = Fraunces({
36 36 variable: "--font-fraunces",
37 37 });
38 38
39 +const SITE_URL = process.env.PUBLIC_URL ?? "https://www.trouve-ka.com";
40 +
39 41 export const metadata: Metadata = {
40 − metadataBase: new URL(process.env.PUBLIC_URL ?? "https://www.trouve-ka.com"),
42 + metadataBase: new URL(SITE_URL),
41 43 title: {
42 − default: "Trouve-Ka — Un service Groupe KA",
43 − template: "%s — Trouve-KA",
44 + default: "Trouve-Ka — moteur de recherche du web québécois",
45 + template: "%s | Trouve-Ka",
44 46 },
45 47 description:
46 48 "Moteur de recherche indépendant du web québécois. Trouve-KA découvre, indexe et classe les sites du Québec — en continu. Un service Groupe KA.",
49 + alternates: {
50 + canonical: "./",
51 + },
47 52 icons: {
48 53 icon: "/favicon.svg",
49 54 apple: "/apple-touch-icon.png",
50 55 },
51 56 openGraph: {
52 − title: "Trouve-Ka — Un service Groupe KA",
57 + title: "Trouve-Ka — moteur de recherche du web québécois",
53 58 description:
54 59 "Moteur de recherche indépendant du web québécois. Trouve-KA découvre, indexe et classe les sites du Québec — en continu. Un service Groupe KA.",
55 60 url: "https://www.trouve-ka.com",
@@ -93,11 +98,43 @@ const LOCAL_FOOTER_LINKS = [
93 98 { href: "/contact", label: "Contact" },
94 99 ];
95 100
101 +// Données structurées Schema.org : le site comme moteur de recherche
102 +// (WebSite + SearchAction pour la boîte de recherche des SERP) + l'organisation.
103 +const JSON_LD = {
104 + "@context": "https://schema.org",
105 + "@graph": [
106 + {
107 + "@type": "WebSite",
108 + name: "Trouve-Ka",
109 + url: SITE_URL,
110 + inLanguage: "fr-CA",
111 + description:
112 + "Moteur de recherche indépendant du web québécois. Trouve-KA découvre, indexe et classe les sites du Québec — en continu.",
113 + potentialAction: {
114 + "@type": "SearchAction",
115 + target: {
116 + "@type": "EntryPoint",
117 + urlTemplate: `${SITE_URL}/search?q={search_term_string}`,
118 + },
119 + "query-input": "required name=search_term_string",
120 + },
121 + publisher: { "@id": `${SITE_URL}/#organization` },
122 + },
123 + {
124 + "@type": "Organization",
125 + "@id": `${SITE_URL}/#organization`,
126 + name: "Trouve-Ka (Groupe KA)",
127 + url: SITE_URL,
128 + logo: `${SITE_URL}/apple-touch-icon.png`,
129 + },
130 + ],
131 +};
132 +
96 133 export default function RootLayout({
97 134 children,
98 135 }: Readonly<{ children: React.ReactNode }>) {
99 136 return (
100 − <html lang="fr">
137 + <html lang="fr-CA">
101 138 <body
102 139 className={`${spaceGrotesk.variable} ${inter.variable} ${jetbrains.variable} ${fraunces.variable} flex min-h-dvh flex-col bg-paper font-body text-ink antialiased`}
103 140 >
@@ -151,6 +188,12 @@ export default function RootLayout({
151 188 <LiveCrawl />
152 189 </KaFooter>
153 190
191 + {/* Données structurées Schema.org (WebSite + SearchAction + Organization) */}
192 + <script
193 + type="application/ld+json"
194 + dangerouslySetInnerHTML={{ __html: JSON.stringify(JSON_LD) }}
195 + />
196 +
154 197 {/* KA Agent — bulle d'assistance IA commune au Groupe KA */}
155 198 <script
156 199 dangerouslySetInnerHTML={{ __html: 'window.KA_AGENT={site:"trouve-ka"}' }}
added apps/web/app/robots.ts +25 −0
@@ -0,0 +1,25 @@
1 +/**
2 + * Trouve-KA — robots.txt (généré par Next App Router)
3 + * Author: Simon-Pierre Boucher
4 + * Contact: contact@spboucher.ai
5 + *
6 + * Standard moteur de recherche : les pages de résultats (/search) sont
7 + * exclues du crawl (espace d'URL infini), tout le reste du site est ouvert.
8 + */
9 +
10 +import type { MetadataRoute } from "next";
11 +
12 +const SITE_URL = process.env.PUBLIC_URL ?? "https://www.trouve-ka.com";
13 +
14 +export default function robots(): MetadataRoute.Robots {
15 + return {
16 + rules: [
17 + {
18 + userAgent: "*",
19 + allow: "/",
20 + disallow: ["/api/", "/admin", "/search"],
21 + },
22 + ],
23 + sitemap: `${SITE_URL}/sitemap.xml`,
24 + };
25 +}
modified apps/web/app/search/page.tsx +6 −1
@@ -17,7 +17,12 @@ export async function generateMetadata({
17 17 }: SearchPageProps): Promise<Metadata> {
18 18 const params = await searchParams;
19 19 const q = typeof params.q === "string" ? params.q.trim() : "";
20 − return { title: q ? q : "Recherche" };
20 + return {
21 + title: q ? q : "Recherche",
22 + // Pages de résultats : jamais indexées (espace d'URL infini) — standard
23 + // des moteurs de recherche, en plus du Disallow /search dans robots.txt.
24 + robots: { index: false, follow: true },
25 + };
21 26 }
22 27
23 28 export default function SearchPage() {
added apps/web/app/sitemap.ts +36 −0
@@ -0,0 +1,36 @@
1 +/**
2 + * Trouve-KA — sitemap.xml (généré par Next App Router)
3 + * Author: Simon-Pierre Boucher
4 + * Contact: contact@spboucher.ai
5 + *
6 + * Pages publiques statiques seulement : les résultats de recherche (/search)
7 + * sont volontairement exclus (noindex + Disallow dans robots.txt).
8 + */
9 +
10 +import type { MetadataRoute } from "next";
11 +
12 +const SITE_URL = process.env.PUBLIC_URL ?? "https://www.trouve-ka.com";
13 +
14 +export default function sitemap(): MetadataRoute.Sitemap {
15 + const lastModified = new Date();
16 +
17 + const routes: Array<{
18 + path: string;
19 + priority: number;
20 + changeFrequency: MetadataRoute.Sitemap[number]["changeFrequency"];
21 + }> = [
22 + { path: "/", priority: 1, changeFrequency: "daily" },
23 + { path: "/trouveka-bot", priority: 0.6, changeFrequency: "monthly" },
24 + { path: "/status", priority: 0.5, changeFrequency: "daily" },
25 + { path: "/stats", priority: 0.5, changeFrequency: "daily" },
26 + { path: "/soumettre", priority: 0.7, changeFrequency: "monthly" },
27 + { path: "/contact", priority: 0.5, changeFrequency: "yearly" },
28 + ];
29 +
30 + return routes.map(({ path, priority, changeFrequency }) => ({
31 + url: `${SITE_URL}${path}`,
32 + lastModified,
33 + changeFrequency,
34 + priority,
35 + }));
36 +}
37