diff --git a/apps/web/app/Poppins-Medium.ttf b/apps/web/app/Poppins-Medium.ttf
new file mode 100644
index 0000000000..5b46f19856
Binary files /dev/null and b/apps/web/app/Poppins-Medium.ttf differ
diff --git a/apps/web/app/Poppins-SemiBold.ttf b/apps/web/app/Poppins-SemiBold.ttf
new file mode 100644
index 0000000000..3bbad2a8be
Binary files /dev/null and b/apps/web/app/Poppins-SemiBold.ttf differ
diff --git a/apps/web/app/collections/[slug]/opengraph-image.tsx b/apps/web/app/collections/[slug]/opengraph-image.tsx
index 991dab2ee2..685c3c3df8 100644
--- a/apps/web/app/collections/[slug]/opengraph-image.tsx
+++ b/apps/web/app/collections/[slug]/opengraph-image.tsx
@@ -1,40 +1,187 @@
import { ImageResponse } from 'next/og';
+import { fetchCollections } from '@/utils/api';
+import { toCollectionSlug } from '@/lib/collections';
+import { listCollectionPreviewRepos } from '@/lib/server/internal-api';
-export const runtime = 'edge';
-export const alt = 'Collection';
-export const size = { width: 800, height: 418 };
+export const runtime = 'nodejs';
+export const alt = 'Collection Rankings - OSSInsight';
+export const size = { width: 1200, height: 630 };
export const dynamic = 'force-dynamic';
export const contentType = 'image/png';
export default async function Image({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
- const name = decodeURIComponent(slug).replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
- const poppinsMedium = fetch(new URL('./Poppins-Medium.ttf', import.meta.url)).then(r => r.arrayBuffer());
- const poppinsSemiBold = fetch(new URL('./Poppins-SemiBold.ttf', import.meta.url)).then(r => r.arrayBuffer());
+ // Resolve collection name from slug
+ let collectionName = decodeURIComponent(slug).replace(/-/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
+ let topRepos: string[] = [];
+
+ try {
+ const collections = await fetchCollections();
+ const collection = collections.find((c) => toCollectionSlug(c.name) === slug);
+ if (collection) {
+ collectionName = collection.name;
+ const previews = await listCollectionPreviewRepos([collection.id]);
+ topRepos = previews
+ .filter((p) => p.collection_id === collection.id)
+ .sort((a, b) => a.repo_rank - b.repo_rank)
+ .slice(0, 5)
+ .map((p) => p.repo_name.split('/')[1] ?? p.repo_name);
+ }
+ } catch {
+ // DB unavailable, use slug-derived name
+ }
+
+ const poppinsMedium = fetch(new URL('./Poppins-Medium.ttf', import.meta.url)).then((r) =>
+ r.arrayBuffer(),
+ );
+ const poppinsSemiBold = fetch(new URL('./Poppins-SemiBold.ttf', import.meta.url)).then((r) =>
+ r.arrayBuffer(),
+ );
return new ImageResponse(
(
-
-
-
-
-
-
COLLECTION
-
{name}
-
Rankings & Trends on OSSInsight
-
ossinsight.io
+
+ {/* Accent top bar */}
+
+ {/* Background glows */}
+
+
+ {/* Label */}
+
+ Collection · OSSInsight
+
+
+ {/* Collection name */}
+
+ {collectionName}
+
+
+ {/* Top repos */}
+ {topRepos.length > 0 && (
+
+ {topRepos.map((repo, i) => (
+
+ {repo}
+
+ ))}
+
+ )}
+
+ {/* Subtitle */}
+
+ Rankings & Trends · Updated in real time
+
+
+ {/* Footer */}
+
+ ossinsight.io
+
),
{
...size,
fonts: [
- { name: 'Poppins', data: await poppinsMedium, style: 'normal' as const, weight: 500 as const },
- { name: 'Poppins', data: await poppinsSemiBold, style: 'normal' as const, weight: 600 as const },
+ {
+ name: 'Poppins',
+ data: await poppinsMedium,
+ style: 'normal' as const,
+ weight: 500 as const,
+ },
+ {
+ name: 'Poppins',
+ data: await poppinsSemiBold,
+ style: 'normal' as const,
+ weight: 600 as const,
+ },
],
},
);
diff --git a/apps/web/app/collections/page.tsx b/apps/web/app/collections/page.tsx
index bc21fdd851..eb8af9ff8f 100644
--- a/apps/web/app/collections/page.tsx
+++ b/apps/web/app/collections/page.tsx
@@ -13,6 +13,14 @@ import { CollectionsList } from './content';
export const metadata: Metadata = {
title: 'Explore Collections',
description: 'Find insights about the monthly or historical rankings and trends in technical fields with curated repository lists.',
+ alternates: {
+ // Canonical strips query params (page, sort, q) to avoid duplicate content
+ canonical: '/collections',
+ },
+ robots: {
+ index: true,
+ follow: true,
+ },
};
export const revalidate = 3600;
diff --git a/apps/web/app/explore/Poppins-Medium.ttf b/apps/web/app/explore/Poppins-Medium.ttf
new file mode 100644
index 0000000000..5b46f19856
Binary files /dev/null and b/apps/web/app/explore/Poppins-Medium.ttf differ
diff --git a/apps/web/app/explore/Poppins-SemiBold.ttf b/apps/web/app/explore/Poppins-SemiBold.ttf
new file mode 100644
index 0000000000..3bbad2a8be
Binary files /dev/null and b/apps/web/app/explore/Poppins-SemiBold.ttf differ
diff --git a/apps/web/app/explore/opengraph-image.tsx b/apps/web/app/explore/opengraph-image.tsx
new file mode 100644
index 0000000000..2009abf7d7
--- /dev/null
+++ b/apps/web/app/explore/opengraph-image.tsx
@@ -0,0 +1,132 @@
+import { ImageResponse } from 'next/og';
+
+export const runtime = 'edge';
+export const alt = 'GitHub Data Explorer - OSSInsight';
+export const size = { width: 1200, height: 630 };
+export const contentType = 'image/png';
+
+export default async function Image() {
+ const poppinsMedium = fetch(new URL('./Poppins-Medium.ttf', import.meta.url)).then((r) =>
+ r.arrayBuffer(),
+ );
+ const poppinsSemiBold = fetch(new URL('./Poppins-SemiBold.ttf', import.meta.url)).then((r) =>
+ r.arrayBuffer(),
+ );
+
+ return new ImageResponse(
+ (
+
+ {/* Accent top bar */}
+
+ {/* Background glow */}
+
+
+ {/* Label */}
+
+ OSSInsight
+
+
+ {/* Title */}
+
+ Data Explorer
+
+
+ {/* Description */}
+
+ Ask questions in plain English · AI-generated SQL · 10B+ GitHub events
+
+
+ {/* Footer */}
+
+ ossinsight.io/explore
+
+
+ ),
+ {
+ ...size,
+ fonts: [
+ {
+ name: 'Poppins',
+ data: await poppinsMedium,
+ style: 'normal' as const,
+ weight: 500 as const,
+ },
+ {
+ name: 'Poppins',
+ data: await poppinsSemiBold,
+ style: 'normal' as const,
+ weight: 600 as const,
+ },
+ ],
+ },
+ );
+}
diff --git a/apps/web/app/home-content.tsx b/apps/web/app/home-content.tsx
index 2e8a99802f..79f15227aa 100644
--- a/apps/web/app/home-content.tsx
+++ b/apps/web/app/home-content.tsx
@@ -1280,7 +1280,7 @@ function HotCollectionsSection() {
+ {/* Accent line */}
+
+
+ {/* 404 number */}
+
+ 404
+
+
+ {/* Heading */}
+
+ Page Not Found
+
+
+ {/* Description */}
+
+ The page you're looking for doesn't exist or may have been moved.
+ Try searching for a repository or exploring our collections.
+
+
+ {/* Action buttons */}
+
+
+ ← Back to Home
+
+
+ 🔍 Search Data Explorer
+
+
+ 📈 Trending Repos
+
+
+
+ {/* Quick links */}
+
+ Try:
+
+ Collections
+
+ ·
+
+ Trending
+
+ ·
+
+ Data Explorer
+
+
+
+ {/* Branding */}
+
+ OSSInsight — Open Source Software Insight
+
+
+ );
+}
diff --git a/apps/web/app/opengraph-image.tsx b/apps/web/app/opengraph-image.tsx
new file mode 100644
index 0000000000..9f5c38c2c1
--- /dev/null
+++ b/apps/web/app/opengraph-image.tsx
@@ -0,0 +1,171 @@
+import { ImageResponse } from 'next/og';
+
+export const runtime = 'edge';
+export const alt = 'OSSInsight - Open Source Software Insight';
+export const size = { width: 1200, height: 630 };
+export const contentType = 'image/png';
+
+export default async function Image() {
+ const poppinsMedium = fetch(new URL('./Poppins-Medium.ttf', import.meta.url)).then((r) =>
+ r.arrayBuffer(),
+ );
+ const poppinsSemiBold = fetch(new URL('./Poppins-SemiBold.ttf', import.meta.url)).then((r) =>
+ r.arrayBuffer(),
+ );
+
+ return new ImageResponse(
+ (
+
+ {/* Accent top bar */}
+
+ {/* Background glow */}
+
+
+
+ {/* Logo / brand */}
+
+
+ {/* Tagline */}
+
+ Open Source Software Insight
+
+
+ {/* Description */}
+
+ Real-time analytics for 10B+ GitHub events · Analyze repos · Compare projects · Explore trends
+
+
+ {/* Footer */}
+
+ ossinsight.io
+
+
+ ),
+ {
+ ...size,
+ fonts: [
+ {
+ name: 'Poppins',
+ data: await poppinsMedium,
+ style: 'normal' as const,
+ weight: 500 as const,
+ },
+ {
+ name: 'Poppins',
+ data: await poppinsSemiBold,
+ style: 'normal' as const,
+ weight: 600 as const,
+ },
+ ],
+ },
+ );
+}
diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx
index d675b07ea0..95e06a60c4 100644
--- a/apps/web/app/page.tsx
+++ b/apps/web/app/page.tsx
@@ -1,6 +1,6 @@
import type { Metadata } from 'next';
import { HomeContent } from './home-content';
-import { FAQPageJsonLd } from '@/components/json-ld';
+import { FAQPageJsonLd, SiteApplicationJsonLd } from '@/components/json-ld';
import { FAQ_ITEMS } from './faq-data';
export const metadata: Metadata = {
@@ -24,6 +24,7 @@ export const metadata: Metadata = {
export default function HomePage() {
return (
<>
+
OSSInsight — Open Source Software Insight
diff --git a/apps/web/app/sitemap.ts b/apps/web/app/sitemap.ts
index b950e8d754..1cc76b0739 100644
--- a/apps/web/app/sitemap.ts
+++ b/apps/web/app/sitemap.ts
@@ -9,11 +9,22 @@ export const dynamic = 'force-dynamic';
export default async function sitemap(): Promise {
const entries: MetadataRoute.Sitemap = [];
- // Static pages
- const staticPages = [
- { path: '/', priority: 1.0 },
- { path: '/explore/', priority: 0.8 },
- { path: '/collections/', priority: 0.8 },
+ // Static pages — canonical URLs without trailing slash for /explore and /collections
+ const staticPages: Array<{ path: string; priority: number; images?: Array<{ url: string; title?: string; caption?: string }> }> = [
+ {
+ path: '/',
+ priority: 1.0,
+ images: [
+ {
+ url: `${SITE_URL}/seo-widgets-homepage.jpeg`,
+ title: 'OSSInsight — Open Source Software Insight',
+ caption: 'Real-time analytics for 10B+ GitHub events',
+ },
+ ],
+ },
+ { path: '/explore', priority: 0.8 },
+ { path: '/collections', priority: 0.8 },
+ { path: '/trending', priority: 0.8 },
];
for (const page of staticPages) {
@@ -21,6 +32,8 @@ export default async function sitemap(): Promise {
url: `${SITE_URL}${page.path}`,
changeFrequency: 'weekly',
priority: page.priority,
+ // @ts-expect-error — Next.js MetadataRoute.Sitemap doesn't type images yet
+ ...(page.images ? { images: page.images } : {}),
});
}
@@ -33,6 +46,13 @@ export default async function sitemap(): Promise {
url: `${SITE_URL}/collections/${slug}`,
changeFrequency: 'weekly',
priority: 0.6,
+ // @ts-expect-error — Next.js MetadataRoute.Sitemap doesn't type images yet
+ images: [
+ {
+ url: `${SITE_URL}/collections/${slug}/opengraph-image`,
+ title: `${collection.name} — GitHub Repository Rankings`,
+ },
+ ],
});
entries.push({
url: `${SITE_URL}/collections/${slug}/trends`,
diff --git a/apps/web/components/json-ld.tsx b/apps/web/components/json-ld.tsx
index bd7fb16fbe..11c5c1df32 100644
--- a/apps/web/components/json-ld.tsx
+++ b/apps/web/components/json-ld.tsx
@@ -277,6 +277,34 @@ export function AggregateRatingJsonLd({
);
}
+export function SiteApplicationJsonLd() {
+ return (
+
+ );
+}
+
export function DatasetJsonLd({
name,
description,
diff --git a/apps/web/next.config.mjs b/apps/web/next.config.mjs
index 738b22cf8c..bc54994ad6 100644
--- a/apps/web/next.config.mjs
+++ b/apps/web/next.config.mjs
@@ -10,6 +10,9 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
/** @type {import('next').NextConfig} */
const nextConfig = {
+ // Disable trailing slashes — canonical URLs have no trailing slash.
+ // Vercel will redirect /path/ → /path automatically when this is false (default).
+ trailingSlash: false,
outputFileTracingRoot: path.join(__dirname, '../../'),
transpilePackages: ['@repo/site-shell'],
typescript: {
diff --git a/configs/collections/10128.google-adk.yml b/configs/collections/10128.google-adk.yml
new file mode 100644
index 0000000000..45bf5e8699
--- /dev/null
+++ b/configs/collections/10128.google-adk.yml
@@ -0,0 +1,16 @@
+id: 10128
+name: Google ADK
+items:
+ - google/adk-python
+ - google/adk-java
+ - google/adk-web
+ - a2aproject/A2A
+ - AIDC-AI/Agentic-ADK
+ - Sri-Krishna-V/awesome-adk-agents
+ - tsubasakong/awesome-google-adk
+ - waldzellai/adk-typescript
+ - RashRAJ/awesome-adk
+ - raphaelmansuy/adk_training
+ - sokart/adk-walkthrough
+ - RubensZimbres/Multi-Agent-System-A2A-ADK-MCP
+ - mongodb-developer/MongoDB-ADK-Agents
diff --git a/configs/collections/10129.neuro-symbolic-ai.yml b/configs/collections/10129.neuro-symbolic-ai.yml
new file mode 100644
index 0000000000..e23286b3ac
--- /dev/null
+++ b/configs/collections/10129.neuro-symbolic-ai.yml
@@ -0,0 +1,12 @@
+id: 10129
+name: Neuro-Symbolic AI
+items:
+ - NucleoidAI/Nucleoid
+ - IBM/neuro-symbolic-ai
+ - IBM/torchlogic
+ - PacktPublishing/Neuro-Symbolic-AI
+ - aika-algorithm/aika-java
+ - nerdimite/neuro-symbolic-ai-soc
+ - anisha2102/awesome-neuro-symbolic-ai
+ - mattfaltyn/awesome-neuro-symbolic-ai
+ - ccclyu/awesome-deeplogic
diff --git a/configs/collections/10130.ai-finops.yml b/configs/collections/10130.ai-finops.yml
new file mode 100644
index 0000000000..a25c1e35ae
--- /dev/null
+++ b/configs/collections/10130.ai-finops.yml
@@ -0,0 +1,13 @@
+id: 10130
+name: AI FinOps
+items:
+ - BerriAI/litellm
+ - mlflow/mlflow
+ - langfuse/langfuse
+ - openlit/openlit
+ - Helicone/helicone
+ - AgentOps-AI/agentops
+ - pydantic/logfire
+ - uptrain-ai/uptrain
+ - traceloop/openllmetry
+ - whylabs/whylogs
diff --git a/configs/collections/10131.synthetic-data.yml b/configs/collections/10131.synthetic-data.yml
new file mode 100644
index 0000000000..7eff63a6ff
--- /dev/null
+++ b/configs/collections/10131.synthetic-data.yml
@@ -0,0 +1,12 @@
+id: 10131
+name: Synthetic Data
+items:
+ - sdv-dev/SDV
+ - gretelai/gretel-synthetics
+ - mostly-ai/mostlyai
+ - Kiln-AI/Kiln
+ - ServiceNow/SyGra
+ - NVIDIA/synthda
+ - Clearbox-AI/clearbox-synthetic-kit
+ - starfishdata/starfish
+ - TonicAI/tonic_validate
diff --git a/configs/collections/10132.ai-quantitative-finance.yml b/configs/collections/10132.ai-quantitative-finance.yml
new file mode 100644
index 0000000000..4f5386469b
--- /dev/null
+++ b/configs/collections/10132.ai-quantitative-finance.yml
@@ -0,0 +1,15 @@
+id: 10132
+name: AI Quantitative Finance
+items:
+ - AI4Finance-Foundation/FinRL
+ - AI4Finance-Foundation/FinGPT
+ - stefan-jansen/machine-learning-for-trading
+ - hudson-and-thames/mlfinlab
+ - vnpy/vnpy
+ - grananqvist/Awesome-Quant-Machine-Learning-Trading
+ - edtechre/pybroker
+ - thuquant/awesome-quant
+ - wangzhe3224/awesome-systematic-trading
+ - fasiondog/hikyuu
+ - alpacahq/alpaca-py
+ - quarkfin/qf-lib
diff --git a/configs/collections/10133.ai-agent-marketplace.yml b/configs/collections/10133.ai-agent-marketplace.yml
new file mode 100644
index 0000000000..72ba1f3f70
--- /dev/null
+++ b/configs/collections/10133.ai-agent-marketplace.yml
@@ -0,0 +1,13 @@
+id: 10133
+name: AI Agent Marketplace
+items:
+ - Significant-Gravitas/AutoGPT
+ - crewAIInc/crewAI
+ - microsoft/autogen
+ - agno-agi/agno
+ - OpenBMB/AgentVerse
+ - THUDM/AgentBench
+ - smol-ai/developer
+ - OpenBMB/ChatDev
+ - letta-ai/letta
+ - SWE-agent/SWE-agent
diff --git a/configs/collections/10134.knowledge-graphs-for-ai.yml b/configs/collections/10134.knowledge-graphs-for-ai.yml
new file mode 100644
index 0000000000..cf057d554b
--- /dev/null
+++ b/configs/collections/10134.knowledge-graphs-for-ai.yml
@@ -0,0 +1,14 @@
+id: 10134
+name: Knowledge Graphs for AI
+items:
+ - microsoft/graphrag
+ - neo4j-labs/llm-graph-builder
+ - OSU-NLP-Group/HippoRAG
+ - FalkorDB/FalkorDB
+ - pingcap/autoflow
+ - Azure-Samples/graphrag-accelerator
+ - topoteretes/cognee
+ - BaranziniLab/KG_RAG
+ - Graph-COM/SubgraphRAG
+ - weaviate/Verba
+ - tomasonjo/diffbot-kg-chatbot
diff --git a/configs/collections/10135.ai-observability.yml b/configs/collections/10135.ai-observability.yml
new file mode 100644
index 0000000000..0daf08eb56
--- /dev/null
+++ b/configs/collections/10135.ai-observability.yml
@@ -0,0 +1,15 @@
+id: 10135
+name: AI Observability
+items:
+ - langfuse/langfuse
+ - mlflow/mlflow
+ - Arize-ai/phoenix
+ - evidentlyai/evidently
+ - traceloop/openllmetry
+ - Helicone/helicone
+ - openlit/openlit
+ - AgentOps-AI/agentops
+ - pydantic/logfire
+ - uptrain-ai/uptrain
+ - whylabs/whylogs
+ - zenml-io/zenml
diff --git a/configs/collections/10136.ai-code-review.yml b/configs/collections/10136.ai-code-review.yml
new file mode 100644
index 0000000000..8e49a31a47
--- /dev/null
+++ b/configs/collections/10136.ai-code-review.yml
@@ -0,0 +1,12 @@
+id: 10136
+name: AI Code Review
+items:
+ - qodo-ai/pr-agent
+ - qodo-ai/qodo-cover
+ - continuedev/continue
+ - microsoft/promptflow
+ - aorwall/moatless-tools
+ - TechNickAI/AICodeBot
+ - SWE-bench/SWE-bench
+ - SWE-agent/SWE-agent
+ - openai/evals
diff --git a/configs/collections/10137.agent-sandboxing.yml b/configs/collections/10137.agent-sandboxing.yml
new file mode 100644
index 0000000000..dcb674bc7e
--- /dev/null
+++ b/configs/collections/10137.agent-sandboxing.yml
@@ -0,0 +1,12 @@
+id: 10137
+name: Agent Sandboxing
+items:
+ - e2b-dev/E2B
+ - e2b-dev/code-interpreter
+ - daytonaio/daytona
+ - SWE-agent/SWE-ReX
+ - microsoft/TaskWeaver
+ - standardagents/arrow-js
+ - abshkbh/arrakis
+ - pipecat-ai/pipecat
+ - modal-labs/modal-client
diff --git a/configs/collections/10138.ai-red-teaming.yml b/configs/collections/10138.ai-red-teaming.yml
new file mode 100644
index 0000000000..f6e2a91fc4
--- /dev/null
+++ b/configs/collections/10138.ai-red-teaming.yml
@@ -0,0 +1,13 @@
+id: 10138
+name: AI Red Teaming
+items:
+ - NVIDIA/garak
+ - promptfoo/promptfoo
+ - Azure/PyRIT
+ - confident-ai/deepteam
+ - llm-attacks/llm-attacks
+ - msoedov/agentic_security
+ - sherdencooper/GPTFuzz
+ - Tencent/AI-Infra-Guard
+ - protectai/rebuff
+ - microsoftarchive/promptbench
diff --git a/configs/collections/10139.a2a-protocol.yml b/configs/collections/10139.a2a-protocol.yml
new file mode 100644
index 0000000000..fdc1566cf4
--- /dev/null
+++ b/configs/collections/10139.a2a-protocol.yml
@@ -0,0 +1,13 @@
+id: 10139
+name: A2A Protocol
+items:
+ - a2aproject/A2A
+ - google/adk-python
+ - google/adk-java
+ - agent-network-protocol/AgentNetworkProtocol
+ - themanojdesai/python-a2a
+ - Coral-Protocol/Anemoi
+ - neuroglia-io/a2a-net
+ - cpp-agan-team/a2a-cpp-sdk
+ - RubensZimbres/Multi-Agent-System-A2A-ADK-MCP
+ - isekOS/awesome-a2a-agents
diff --git a/configs/collections/10140.google-adk-python.yml b/configs/collections/10140.google-adk-python.yml
new file mode 100644
index 0000000000..a24c624fc4
--- /dev/null
+++ b/configs/collections/10140.google-adk-python.yml
@@ -0,0 +1,15 @@
+id: 10140
+name: Google ADK Python
+items:
+ - google/adk-python
+ - google/adk-web
+ - google/adk-java
+ - a2aproject/A2A
+ - Sri-Krishna-V/awesome-adk-agents
+ - raphaelmansuy/adk_training
+ - arjunprabhulal/adk-vertex-ai-rag-engine
+ - sokart/adk-walkthrough
+ - RashRAJ/awesome-adk
+ - tsubasakong/awesome-google-adk
+ - ksmin23/my-adk-python-samples
+ - mongodb-developer/MongoDB-ADK-Agents
diff --git a/configs/collections/10141.agent-harness.yml b/configs/collections/10141.agent-harness.yml
new file mode 100644
index 0000000000..8aac4879d5
--- /dev/null
+++ b/configs/collections/10141.agent-harness.yml
@@ -0,0 +1,15 @@
+id: 10141
+name: Agent Harness
+items:
+ - microsoft/autogen
+ - crewAIInc/crewAI
+ - agno-agi/agno
+ - letta-ai/letta
+ - pipecat-ai/pipecat
+ - microsoft/TaskWeaver
+ - microsoft/promptflow
+ - run-llama/llama_deploy
+ - SWE-agent/SWE-agent
+ - THUDM/AgentBench
+ - AgentOps-AI/agentops
+ - openai/evals