GitHub Trending Data
Fetch trending repositories and developers from GitHub.
Important Note
GitHub does NOT provide an official trending API. This skill uses web scraping or GitHub Search API as alternatives.
Method 1: Web Scraping (Recommended)
typescript
1import * as cheerio from 'cheerio';
2
3interface TrendingRepo {
4 owner: string;
5 name: string;
6 url: string;
7 description: string;
8 language: string;
9 stars: number;
10 forks: number;
11 todayStars: number;
12}
13
14async function getTrendingRepos(
15 language = '',
16 since = 'daily' // 'daily', 'weekly', 'monthly'
17): Promise<TrendingRepo[]> {
18 const url = language
19 ? `https://github.com/trending/${language}?since=${since}`
20 : `https://github.com/trending?since=${since}`;
21
22 const response = await fetch(url, {
23 headers: {
24 'User-Agent': 'Mozilla/5.0 (compatible; TrendingBot/1.0)'
25 }
26 });
27
28 const html = await response.text();
29 const $ = cheerio.load(html);
30 const repos: TrendingRepo[] = [];
31
32 $('article.Box-row').each((_, element) => {
33 const $el = $(element);
34 const $title = $el.find('h2 a');
35 const fullName = $title.attr('href')?.slice(1) || '';
36 const [owner, name] = fullName.split('/');
37
38 const description = $el.find('p').text().trim();
39 const language = $el.find('[itemprop="programmingLanguage"]').text().trim();
40
41 const starsText = $el.find('a[href*="stargazers"]').text().trim();
42 const stars = parseNumber(starsText);
43
44 const forksText = $el.find('a[href*="forks"]').text().trim();
45 const forks = parseNumber(forksText);
46
47 const todayStarsText = $el.find('span.float-sm-right').text().trim();
48 const todayStars = parseNumber(todayStarsText);
49
50 repos.push({
51 owner,
52 name,
53 url: `https://github.com${$title.attr('href')}`,
54 description,
55 language,
56 stars,
57 forks,
58 todayStars,
59 });
60 });
61
62 return repos;
63}
64
65function parseNumber(text: string): number {
66 const cleaned = text.replace(/,/g, '');
67 const match = cleaned.match(/[\d.]+/);
68 if (!match) return 0;
69
70 const num = parseFloat(match[0]);
71 if (text.includes('k')) return Math.round(num * 1000);
72 if (text.includes('m')) return Math.round(num * 1000000);
73 return Math.round(num);
74}
Method 2: GitHub Search API
typescript
1interface GitHubRepo {
2 name: string;
3 owner: { login: string };
4 html_url: string;
5 description: string;
6 language: string;
7 stargazers_count: number;
8 forks_count: number;
9 created_at: string;
10}
11
12async function getTrendingViaSearch(
13 language = '',
14 token?: string
15): Promise<GitHubRepo[]> {
16 const date = new Date();
17 date.setDate(date.getDate() - 7); // Last week
18 const since = date.toISOString().split('T')[0];
19
20 const query = [
21 `created:>${since}`,
22 'stars:>100',
23 language ? `language:${language}` : '',
24 ].filter(Boolean).join(' ');
25
26 const headers: HeadersInit = {
27 'Accept': 'application/vnd.github.v3+json',
28 };
29
30 if (token) {
31 headers['Authorization'] = `Bearer ${token}`;
32 }
33
34 const response = await fetch(
35 `https://api.github.com/search/repositories?q=${encodeURIComponent(query)}&sort=stars&order=desc`,
36 { headers }
37 );
38
39 const data = await response.json();
40 return data.items;
41}
Next.js API Route
typescript
1// app/api/trending/route.ts
2import { NextResponse } from 'next/server';
3import * as cheerio from 'cheerio';
4
5export const dynamic = 'force-dynamic';
6
7export async function GET(request: Request) {
8 const { searchParams } = new URL(request.url);
9 const language = searchParams.get('language') || '';
10 const since = searchParams.get('since') || 'daily';
11
12 try {
13 const repos = await getTrendingRepos(language, since);
14
15 return NextResponse.json(
16 { success: true, data: repos },
17 {
18 headers: {
19 'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=1800',
20 },
21 }
22 );
23 } catch (error) {
24 return NextResponse.json(
25 { success: false, error: 'Failed to fetch trending repos' },
26 { status: 500 }
27 );
28 }
29}
React Component
tsx
1'use client';
2
3import { useState, useEffect } from 'react';
4
5interface Repo {
6 owner: string;
7 name: string;
8 url: string;
9 description: string;
10 language: string;
11 stars: number;
12 todayStars: number;
13}
14
15export function TrendingRepos() {
16 const [repos, setRepos] = useState<Repo[]>([]);
17 const [loading, setLoading] = useState(true);
18
19 useEffect(() => {
20 fetch('/api/trending')
21 .then(res => res.json())
22 .then(data => {
23 setRepos(data.data);
24 setLoading(false);
25 });
26 }, []);
27
28 if (loading) return <div>Loading...</div>;
29
30 return (
31 <div className="space-y-4">
32 {repos.map(repo => (
33 <div key={repo.url} className="border rounded-lg p-4">
34 <a
35 href={repo.url}
36 target="_blank"
37 rel="noopener noreferrer"
38 className="text-blue-600 hover:underline font-semibold"
39 >
40 {repo.owner}/{repo.name}
41 </a>
42
43 <p className="text-gray-600 mt-2">{repo.description}</p>
44
45 <div className="flex items-center gap-4 mt-3 text-sm text-gray-500">
46 {repo.language && (
47 <span className="flex items-center gap-1">
48 <span className="w-3 h-3 rounded-full bg-blue-500" />
49 {repo.language}
50 </span>
51 )}
52
53 <span>⭐ {repo.stars.toLocaleString()}</span>
54
55 <span className="text-green-600">
56 +{repo.todayStars.toLocaleString()} today
57 </span>
58 </div>
59 </div>
60 ))}
61 </div>
62 );
63}
Caching Strategy
typescript
1// Cache for 1 hour
2const CACHE_TTL = 3600000; // 1 hour in ms
3const cache = new Map<string, { data: any; timestamp: number }>();
4
5async function getCachedTrending(language: string, since: string) {
6 const key = `${language}-${since}`;
7 const cached = cache.get(key);
8
9 if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
10 return cached.data;
11 }
12
13 const data = await getTrendingRepos(language, since);
14 cache.set(key, { data, timestamp: Date.now() });
15
16 return data;
17}
Important Warnings
- GitHub may change their HTML - Monitor for breakages
- Rate limiting - Use aggressive caching (1 hour minimum)
- User-Agent required - Always include User-Agent header
- CORS - Implement server-side to avoid CORS issues
Rate Limits
Scraping: No official limits, but be respectful
GitHub API:
- Unauthenticated: 10 requests/minute
- Authenticated: 30 requests/minute
Resources