Skip to content

Commit acae3eb

Browse files
authored
Merge pull request #783 from zackproser/improve-comparisons-again
Add comparison pages to sitemap
2 parents 1dbe023 + 2acaee7 commit acae3eb

File tree

4 files changed

+190
-24
lines changed

4 files changed

+190
-24
lines changed

src/app/comparisons/[tool1]/vs/[tool2]/page.tsx

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Suspense } from 'react'
2-
import { notFound } from 'next/navigation'
2+
import { notFound, redirect } from 'next/navigation'
33
import { Metadata } from 'next'
44
import { getToolBySlug, getAllTools } from '@/actions/tool-actions'
55
import ComparisonPageLayout from '@/components/ComparisonPageLayout'
@@ -14,44 +14,47 @@ interface PageProps {
1414
}>
1515
}
1616

17-
// Generate static params for all possible tool combinations
17+
// Helper function to create slug from tool name
18+
const createSlug = (name: string) => {
19+
return name
20+
.toLowerCase()
21+
.replace(/[^a-z0-9\s-]/g, '') // Remove special characters except spaces and hyphens
22+
.replace(/\s+/g, '-') // Replace spaces with hyphens
23+
.replace(/-+/g, '-') // Replace multiple hyphens with single hyphen
24+
.replace(/^-|-$/g, '') // Remove leading/trailing hyphens
25+
}
26+
27+
// Generate static params for all possible tool combinations (canonical direction only)
1828
export async function generateStaticParams() {
1929
try {
2030
const tools = await getAllTools()
2131
const params = []
2232

23-
// Generate all possible combinations of tools
33+
// Generate combinations in canonical order only (alphabetical by slug)
2434
for (let i = 0; i < tools.length; i++) {
2535
for (let j = i + 1; j < tools.length; j++) {
2636
const tool1 = tools[i]
2737
const tool2 = tools[j]
2838

29-
// Create slug from tool name
30-
const createSlug = (name: string) => {
31-
return name
32-
.toLowerCase()
33-
.replace(/[^a-z0-9\s-]/g, '') // Remove special characters except spaces and hyphens
34-
.replace(/\s+/g, '-') // Replace spaces with hyphens
35-
.replace(/-+/g, '-') // Replace multiple hyphens with single hyphen
36-
.replace(/^-|-$/g, '') // Remove leading/trailing hyphens
37-
}
38-
3939
const tool1Slug = createSlug(tool1.name)
4040
const tool2Slug = createSlug(tool2.name)
4141

42-
// Add both combinations (tool1 vs tool2 and tool2 vs tool1)
43-
params.push({
44-
tool1: tool1Slug,
45-
tool2: tool2Slug
46-
})
47-
params.push({
48-
tool1: tool2Slug,
49-
tool2: tool1Slug
50-
})
42+
// Only add the alphabetically first combination to avoid duplicates
43+
if (tool1Slug < tool2Slug) {
44+
params.push({
45+
tool1: tool1Slug,
46+
tool2: tool2Slug
47+
})
48+
} else {
49+
params.push({
50+
tool1: tool2Slug,
51+
tool2: tool1Slug
52+
})
53+
}
5154
}
5255
}
5356

54-
console.log(`Generated ${params.length} static params for comparison routes`)
57+
console.log(`Generated ${params.length} static params for comparison routes (canonical direction only)`)
5558
return params
5659
} catch (error) {
5760
console.error('Error generating static params for comparisons:', error)
@@ -92,6 +95,11 @@ export async function generateMetadata({ params }: PageProps): Promise<Metadata>
9295

9396
async function ComparisonContent({ tool1Slug, tool2Slug }: { tool1Slug: string, tool2Slug: string }) {
9497
try {
98+
// Check if this is the canonical URL order, if not redirect
99+
if (tool1Slug > tool2Slug) {
100+
redirect(`/comparisons/${tool2Slug}/vs/${tool1Slug}`)
101+
}
102+
95103
const [tool1, tool2] = await Promise.all([
96104
getToolBySlug(tool1Slug),
97105
getToolBySlug(tool2Slug)

src/app/rss/feed.json/route.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
import { Feed } from 'feed'
22
import { getAllContent } from '@/lib/content-handlers'
3+
import { getAllTools } from '@/actions/tool-actions'
4+
5+
// Helper function to create slug from tool name (same logic as in comparison page)
6+
function createSlug(name) {
7+
return name
8+
.toLowerCase()
9+
.replace(/[^a-z0-9\s-]/g, '') // Remove special characters except spaces and hyphens
10+
.replace(/\s+/g, '-') // Replace spaces with hyphens
11+
.replace(/-+/g, '-') // Replace multiple hyphens with single hyphen
12+
.replace(/^-|-$/g, '') // Remove leading/trailing hyphens
13+
}
314

415
export async function GET() {
516
let siteUrl = process.env.NEXT_PUBLIC_SITE_URL
@@ -54,6 +65,51 @@ export async function GET() {
5465
}
5566
}
5667

68+
// Add comparison routes to feed
69+
try {
70+
console.log('Adding comparison routes to JSON feed...');
71+
const tools = await getAllTools();
72+
73+
// Generate comparison entries (limit to avoid overwhelming the feed, canonical order only)
74+
const comparisonEntries = [];
75+
for (let i = 0; i < tools.length && comparisonEntries.length < 50; i++) {
76+
for (let j = i + 1; j < tools.length && comparisonEntries.length < 50; j++) {
77+
const tool1 = tools[i];
78+
const tool2 = tools[j];
79+
80+
const tool1Slug = createSlug(tool1.name);
81+
const tool2Slug = createSlug(tool2.name);
82+
83+
// Only add the alphabetically first combination to avoid duplicates
84+
let comparisonUrl;
85+
let title;
86+
if (tool1Slug < tool2Slug) {
87+
comparisonUrl = `${siteUrl}/comparisons/${tool1Slug}/vs/${tool2Slug}`;
88+
title = `${tool1.name} vs ${tool2.name}`;
89+
} else {
90+
comparisonUrl = `${siteUrl}/comparisons/${tool2Slug}/vs/${tool1Slug}`;
91+
title = `${tool2.name} vs ${tool1.name}`;
92+
}
93+
94+
comparisonEntries.push({
95+
title: title,
96+
id: comparisonUrl,
97+
link: comparisonUrl,
98+
author: [author],
99+
contributor: [author],
100+
date: new Date(), // Use current date for comparisons
101+
description: `Compare ${tool1.name} and ${tool2.name} - features, pricing, pros and cons. Find the best tool for your development needs.`,
102+
});
103+
}
104+
}
105+
106+
// Add comparison entries to feed
107+
comparisonEntries.forEach(entry => feed.addItem(entry));
108+
console.log(`Added ${comparisonEntries.length} canonical comparison entries to JSON feed`);
109+
} catch (error) {
110+
console.error('Error adding comparison routes to JSON feed:', error);
111+
}
112+
57113
return new Response(feed.json1(), {
58114
status: 200,
59115
headers: {

src/app/rss/feed.xml/route.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
import { Feed } from 'feed'
22
import { getAllContent } from '@/lib/content-handlers'
3+
import { getAllTools } from '@/actions/tool-actions'
4+
5+
// Helper function to create slug from tool name (same logic as in comparison page)
6+
function createSlug(name) {
7+
return name
8+
.toLowerCase()
9+
.replace(/[^a-z0-9\s-]/g, '') // Remove special characters except spaces and hyphens
10+
.replace(/\s+/g, '-') // Replace spaces with hyphens
11+
.replace(/-+/g, '-') // Replace multiple hyphens with single hyphen
12+
.replace(/^-|-$/g, '') // Remove leading/trailing hyphens
13+
}
314

415
export async function GET() {
516
try {
@@ -81,6 +92,57 @@ export async function GET() {
8192
}
8293
}
8394

95+
// Add comparison routes to feed
96+
try {
97+
console.log('Adding comparison routes to XML feed...');
98+
const tools = await getAllTools();
99+
100+
// Generate comparison entries (limit to avoid overwhelming the feed, canonical order only)
101+
const comparisonEntries = [];
102+
for (let i = 0; i < tools.length && comparisonEntries.length < 50; i++) {
103+
for (let j = i + 1; j < tools.length && comparisonEntries.length < 50; j++) {
104+
const tool1 = tools[i];
105+
const tool2 = tools[j];
106+
107+
const tool1Slug = createSlug(tool1.name);
108+
const tool2Slug = createSlug(tool2.name);
109+
110+
// Only add the alphabetically first combination to avoid duplicates
111+
let comparisonUrl;
112+
let title;
113+
if (tool1Slug < tool2Slug) {
114+
comparisonUrl = `${siteUrl}/comparisons/${tool1Slug}/vs/${tool2Slug}`;
115+
title = `${tool1.name} vs ${tool2.name}`;
116+
} else {
117+
comparisonUrl = `${siteUrl}/comparisons/${tool2Slug}/vs/${tool1Slug}`;
118+
title = `${tool2.name} vs ${tool1.name}`;
119+
}
120+
121+
comparisonEntries.push({
122+
title: title,
123+
id: comparisonUrl,
124+
link: comparisonUrl,
125+
author: [author],
126+
contributor: [author],
127+
date: new Date(), // Use current date for comparisons
128+
description: `Compare ${tool1.name} and ${tool2.name} - features, pricing, pros and cons. Find the best tool for your development needs.`,
129+
});
130+
}
131+
}
132+
133+
// Add comparison entries to feed
134+
comparisonEntries.forEach(entry => {
135+
try {
136+
feed.addItem(entry);
137+
} catch (itemError) {
138+
console.error(`Error adding comparison item ${entry.title}:`, itemError);
139+
}
140+
});
141+
console.log(`Added ${comparisonEntries.length} canonical comparison entries to XML feed`);
142+
} catch (error) {
143+
console.error('Error adding comparison routes to XML feed:', error);
144+
}
145+
84146
// Generate XML with error handling
85147
try {
86148
console.log('Generating XML feed...');

src/app/sitemap.js

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import fs from 'fs';
22
import path from 'path';
33
import { getAllContent, getAllProducts, getAppPageRoutesPaths } from '@/lib/content-handlers';
4+
import { getAllTools } from '@/actions/tool-actions';
45
// import { siteConfig } from '@/config/site'; // Removed unused import
56

67
const baseUrl = process.env.SITE_URL || 'https://zackproser.com';
@@ -11,6 +12,16 @@ const dynamicDetailDirs = [
1112
{ base: 'vectordatabases', detail: 'detail', jsonFile: 'vectordatabases.json', key: 'databases' }
1213
];
1314

15+
// Helper function to create slug from tool name (same logic as in comparison page)
16+
function createSlug(name) {
17+
return name
18+
.toLowerCase()
19+
.replace(/[^a-z0-9\s-]/g, '') // Remove special characters except spaces and hyphens
20+
.replace(/\s+/g, '-') // Replace spaces with hyphens
21+
.replace(/-+/g, '-') // Replace multiple hyphens with single hyphen
22+
.replace(/^-|-$/g, '') // Remove leading/trailing hyphens
23+
}
24+
1425
async function getRoutes() {
1526
const routes = new Set();
1627

@@ -66,13 +77,42 @@ async function getRoutes() {
6677
}
6778
});
6879

80+
// Add all comparison routes
81+
try {
82+
console.log('Generating comparison routes for sitemap...');
83+
const tools = await getAllTools();
84+
console.log(`Found ${tools.length} tools for comparison routes`);
85+
86+
// Generate combinations in canonical order only (alphabetical by slug) to avoid duplicates
87+
for (let i = 0; i < tools.length; i++) {
88+
for (let j = i + 1; j < tools.length; j++) {
89+
const tool1 = tools[i];
90+
const tool2 = tools[j];
91+
92+
const tool1Slug = createSlug(tool1.name);
93+
const tool2Slug = createSlug(tool2.name);
94+
95+
// Only add the alphabetically first combination to avoid duplicates
96+
if (tool1Slug < tool2Slug) {
97+
routes.add(`/comparisons/${tool1Slug}/vs/${tool2Slug}`);
98+
} else {
99+
routes.add(`/comparisons/${tool2Slug}/vs/${tool1Slug}`);
100+
}
101+
}
102+
}
103+
104+
console.log(`Added canonical comparison routes to sitemap`);
105+
} catch (error) {
106+
console.error('Error generating comparison routes for sitemap:', error);
107+
}
108+
69109
// Add RSS feed routes
70110
routes.add('/rss/feed.json');
71111
routes.add('/rss/feed.xml');
72112

73113
// Convert Set to Array and log the routes for debugging
74114
const uniqueRoutes = Array.from(routes);
75-
console.log('Generated routes:', uniqueRoutes);
115+
console.log(`Generated ${uniqueRoutes.length} total routes for sitemap`);
76116

77117
return uniqueRoutes.map(route => ({
78118
url: `${baseUrl}${route}`,

0 commit comments

Comments
 (0)