This commit is contained in:
@@ -1,13 +1,7 @@
|
||||
import { BASE_URL } from '@/lib/constants';
|
||||
import { type MetadataRoute } from 'next';
|
||||
|
||||
export default function sitemap(): MetadataRoute.Sitemap {
|
||||
return [
|
||||
{
|
||||
url: BASE_URL,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'yearly',
|
||||
priority: 1,
|
||||
},
|
||||
];
|
||||
import { buildSitemapEntries } from '@/lib/sitemap';
|
||||
|
||||
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
||||
return buildSitemapEntries();
|
||||
}
|
||||
|
||||
23
src/lib/__tests__/sitemap.test.ts
Normal file
23
src/lib/__tests__/sitemap.test.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { buildSitemapEntries } from '../sitemap';
|
||||
|
||||
describe('buildSitemapEntries', () => {
|
||||
it('includes known static routes', async () => {
|
||||
const sitemap = await buildSitemapEntries();
|
||||
const urls = sitemap.map((entry) => entry.url);
|
||||
|
||||
expect(urls).toContain('https://investingfire.com/');
|
||||
expect(urls).toContain('https://investingfire.com/learn');
|
||||
expect(urls).toContain('https://investingfire.com/learn/what-is-fire');
|
||||
expect(sitemap.every((entry) => entry.lastModified instanceof Date)).toBe(true);
|
||||
});
|
||||
|
||||
it('omits metadata routes from the sitemap output', async () => {
|
||||
const sitemap = await buildSitemapEntries();
|
||||
const urls = sitemap.map((entry) => entry.url);
|
||||
|
||||
expect(urls.some((url) => url.includes('sitemap'))).toBe(false);
|
||||
expect(urls.some((url) => url.includes('robots'))).toBe(false);
|
||||
});
|
||||
});
|
||||
75
src/lib/sitemap.ts
Normal file
75
src/lib/sitemap.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { promises as fs } from 'fs';
|
||||
import path from 'path';
|
||||
import { type MetadataRoute } from 'next';
|
||||
|
||||
import { BASE_URL } from '@/lib/constants';
|
||||
|
||||
interface PageRoute {
|
||||
pathname: string;
|
||||
lastModified: Date;
|
||||
}
|
||||
|
||||
const PAGE_FILE_PATTERN = /^page\.(mdx|tsx?|jsx?)$/;
|
||||
const EXCLUDED_DIRECTORIES = new Set(['components', '__tests__', 'api']);
|
||||
const APP_DIR = path.join(process.cwd(), 'src', 'app');
|
||||
|
||||
const isRouteGroup = (name: string) => name.startsWith('(') && name.endsWith(')');
|
||||
const shouldSkipDirectory = (name: string) =>
|
||||
EXCLUDED_DIRECTORIES.has(name) || name.startsWith('_') || name.startsWith('.') || name.includes('[');
|
||||
|
||||
async function discoverPages(currentDir: string, segments: string[] = []): Promise<PageRoute[]> {
|
||||
const entries = await fs.readdir(currentDir, { withFileTypes: true });
|
||||
const pages: PageRoute[] = [];
|
||||
|
||||
for (const entry of entries) {
|
||||
const entryPath = path.join(currentDir, entry.name);
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
if (shouldSkipDirectory(entry.name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const nextSegments = isRouteGroup(entry.name) ? segments : [...segments, entry.name];
|
||||
const childPages = await discoverPages(entryPath, nextSegments);
|
||||
pages.push(...childPages);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.isFile() && PAGE_FILE_PATTERN.test(entry.name)) {
|
||||
const pathname = segments.length === 0 ? '/' : `/${segments.join('/')}`;
|
||||
const stats = await fs.stat(entryPath);
|
||||
|
||||
pages.push({ pathname, lastModified: stats.mtime });
|
||||
}
|
||||
}
|
||||
|
||||
return pages;
|
||||
}
|
||||
|
||||
function toAbsoluteUrl(pathname: string): string {
|
||||
const normalized = pathname === '/' ? '' : pathname;
|
||||
return new URL(normalized, BASE_URL).toString();
|
||||
}
|
||||
|
||||
export async function buildSitemapEntries(): Promise<MetadataRoute.Sitemap> {
|
||||
const pages = await discoverPages(APP_DIR);
|
||||
|
||||
const uniquePages = new Map<string, PageRoute>();
|
||||
for (const page of pages) {
|
||||
const existing = uniquePages.get(page.pathname);
|
||||
if (!existing || existing.lastModified < page.lastModified) {
|
||||
uniquePages.set(page.pathname, page);
|
||||
}
|
||||
}
|
||||
|
||||
const sortedPages = Array.from(uniquePages.values()).sort((a, b) =>
|
||||
a.pathname.localeCompare(b.pathname),
|
||||
);
|
||||
|
||||
return sortedPages.map(({ pathname, lastModified }) => ({
|
||||
url: toAbsoluteUrl(pathname),
|
||||
lastModified,
|
||||
changeFrequency: 'weekly',
|
||||
priority: pathname === '/' ? 1 : 0.8,
|
||||
}));
|
||||
}
|
||||
Reference in New Issue
Block a user