Skip to content

Commit 77d0352

Browse files
authored
feat(www): add sitemap and robots.txt (#5)
1 parent 8925c28 commit 77d0352

File tree

5 files changed

+223
-2
lines changed

5 files changed

+223
-2
lines changed

apps/www/.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,7 @@ yarn-error.log*
2525
# others
2626
.env*.local
2727
.vercel
28-
next-env.d.ts
28+
next-env.d.ts
29+
30+
# sitemap
31+
/public/sitemap.xml

apps/www/app/robots.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
User-Agent: *
2+
Allow: /
3+
Disallow: /private/
4+
5+
Sitemap: https://amical.ai/sitemap.xml

apps/www/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
"version": "0.0.0",
44
"private": true,
55
"scripts": {
6-
"build": "next build",
6+
"build": "pnpm build:sitemap && next build",
7+
"build:sitemap": "pnpm exec tsx ./scripts/generate-sitemap.mts",
78
"dev": "next dev --turbo",
89
"start": "next start",
910
"serve": "pnpm dlx serve out -p 3000",
@@ -36,9 +37,11 @@
3637
"@types/node": "22.15.12",
3738
"@types/react": "^19.1.3",
3839
"@types/react-dom": "^19.1.3",
40+
"globby": "^14.1.0",
3941
"postcss": "^8.5.3",
4042
"server": "^1.0.41",
4143
"tailwindcss": "^4.1.5",
44+
"tsx": "^4.19.4",
4245
"tw-animate-css": "^1.2.9",
4346
"typescript": "^5.8.3"
4447
}

apps/www/scripts/generate-sitemap.mts

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import fs from 'fs';
2+
import { globby } from 'globby';
3+
import prettier from 'prettier';
4+
import path from 'path';
5+
6+
async function generate() {
7+
const prettierConfig = await prettier.resolveConfig('./.prettierrc');
8+
9+
// Ensure directories exist
10+
fs.mkdirSync('public', { recursive: true });
11+
fs.mkdirSync('out', { recursive: true });
12+
13+
// Get static pages
14+
const pages = await globby([
15+
'app/**/page.tsx',
16+
'!app/**/_*/**',
17+
'!app/**/api/**',
18+
'!app/docs/**', // Exclude docs directory as we'll handle it separately
19+
]);
20+
21+
// Get doc pages from the build output
22+
const docPages = await globby(['out/docs/**/*.html'])
23+
.then(pages => pages
24+
.map(page => page
25+
.replace('out', '')
26+
.replace('/index.html', '')
27+
.replace('.html', '')
28+
)
29+
.filter(page => !page.includes('/_'))
30+
);
31+
32+
// Get blog pages from the build output
33+
const blogPages = await globby(['out/blog/**/*.html'])
34+
.then(pages => pages
35+
.map(page => page
36+
.replace('out', '')
37+
.replace('/index.html', '')
38+
.replace('.html', '')
39+
)
40+
.filter(page => !page.includes('/_') && !page.includes('/blog/index'))
41+
);
42+
43+
const baseUrl = 'https://amical.ai';
44+
45+
const sitemap = `
46+
<?xml version="1.0" encoding="UTF-8"?>
47+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
48+
${[
49+
// Add static pages
50+
...pages.map((page) => {
51+
const path = page
52+
.replace('app', '')
53+
.replace('/page.tsx', '')
54+
.replace('/(home)', '')
55+
.replace(/\[\[\.\.\..*?\]\]/, '');
56+
57+
// Skip dynamic routes with parameters
58+
if (path.includes('[') || path.includes(']')) {
59+
return '';
60+
}
61+
62+
const route = path === '' ? '' : path;
63+
64+
return `
65+
<url>
66+
<loc>${baseUrl}${route}</loc>
67+
<lastmod>${new Date().toISOString()}</lastmod>
68+
<changefreq>daily</changefreq>
69+
<priority>0.7</priority>
70+
</url>
71+
`;
72+
}),
73+
// Add docs index page
74+
`
75+
<url>
76+
<loc>${baseUrl}/docs</loc>
77+
<lastmod>${new Date().toISOString()}</lastmod>
78+
<changefreq>daily</changefreq>
79+
<priority>0.7</priority>
80+
</url>
81+
`,
82+
// Add doc pages
83+
...docPages.map((path) => `
84+
<url>
85+
<loc>${baseUrl}${path}</loc>
86+
<lastmod>${new Date().toISOString()}</lastmod>
87+
<changefreq>daily</changefreq>
88+
<priority>0.7</priority>
89+
</url>
90+
`),
91+
// Add blog index page
92+
`
93+
<url>
94+
<loc>${baseUrl}/blog</loc>
95+
<lastmod>${new Date().toISOString()}</lastmod>
96+
<changefreq>weekly</changefreq>
97+
<priority>0.8</priority>
98+
</url>
99+
`,
100+
// Add blog pages
101+
...blogPages.map((path) => `
102+
<url>
103+
<loc>${baseUrl}${path}</loc>
104+
<lastmod>${new Date().toISOString()}</lastmod>
105+
<changefreq>weekly</changefreq>
106+
<priority>0.7</priority>
107+
</url>
108+
`),
109+
]
110+
.filter(Boolean)
111+
.join('')}
112+
</urlset>
113+
`;
114+
115+
const formatted = await prettier.format(sitemap, {
116+
...prettierConfig,
117+
parser: 'html',
118+
});
119+
120+
fs.writeFileSync('public/sitemap.xml', formatted);
121+
fs.writeFileSync('out/sitemap.xml', formatted);
122+
123+
console.log('✅ Generated sitemap.xml');
124+
}
125+
126+
generate().catch((err) => {
127+
console.error(err);
128+
process.exit(1);
129+
});

0 commit comments

Comments
 (0)