Skip to content

Commit f1b3fa9

Browse files
committed
Replace dynamic sitemap with build-time static generation using git lastmod dates
Generate sitemap.xml and robots.txt at build time via RadzenBlazorDemos.Tools instead of serving them dynamically through SitemapController and Razor pages. Uses git log to embed accurate per-page last-modified dates.
1 parent 13ec932 commit f1b3fa9

File tree

6 files changed

+154
-143
lines changed

6 files changed

+154
-143
lines changed

.github/workflows/deploy.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,20 @@ jobs:
2222
steps:
2323
- name: Checkout
2424
uses: actions/checkout@v4
25+
with:
26+
fetch-depth: 0
27+
28+
- name: Set up .NET
29+
uses: actions/setup-dotnet@v4
30+
with:
31+
dotnet-version: '10.0.x'
32+
33+
- name: Generate static assets
34+
run: |
35+
dotnet run --project RadzenBlazorDemos.Tools -- \
36+
RadzenBlazorDemos.Host/wwwroot \
37+
RadzenBlazorDemos/Pages \
38+
RadzenBlazorDemos/Services/ExampleService.cs
2539
2640
- name: Set up Docker Buildx
2741
uses: docker/setup-buildx-action@v3

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,3 +349,5 @@ Radzen.Blazor.min.js
349349
/.gitignore
350350
RadzenBlazorDemos/wwwroot/demos
351351
llms.txt
352+
RadzenBlazorDemos.Host/wwwroot/sitemap.xml
353+
RadzenBlazorDemos.Host/wwwroot/robots.txt

RadzenBlazorDemos.Host/Controllers/SitemapController.cs

Lines changed: 0 additions & 70 deletions
This file was deleted.

RadzenBlazorDemos.Host/Pages/Examples.cshtml

Lines changed: 0 additions & 57 deletions
This file was deleted.

RadzenBlazorDemos.Host/Pages/Sitemap.cshtml

Lines changed: 0 additions & 16 deletions
This file was deleted.

RadzenBlazorDemos.Tools/Program.cs

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Diagnostics;
12
using System.Net;
23
using System.Text;
34
using System.Text.RegularExpressions;
@@ -117,6 +118,7 @@ static int Main(string[] args)
117118

118119
GenerateComponentPages(categories, pagesPath, xmlDocs, typeMap, mdDir);
119120
GenerateIndex(categories, Path.Combine(outputDir, "llms.txt"), xmlDocs);
121+
GenerateSitemap(categories, pagesPath, outputDir);
120122

121123
var apiCount = Directory.Exists(Path.Combine(mdDir, "api")) ? Directory.GetFiles(Path.Combine(mdDir, "api"), "*.md").Length : 0;
122124
var pageCount = Directory.GetFiles(mdDir, "*.md").Length;
@@ -1358,4 +1360,140 @@ static string CleanExampleFile(string content)
13581360

13591361
return result.Trim();
13601362
}
1363+
1364+
// ── Sitemap generation ──────────────────────────────────────────────
1365+
1366+
static void GenerateSitemap(List<ExampleNode> categories, string pagesPath, string outputDir)
1367+
{
1368+
var routeFileMap = BuildRouteFileMap(pagesPath);
1369+
var gitRoot = FindGitRoot(pagesPath);
1370+
1371+
var urls = new List<(string Url, string Lastmod)>();
1372+
1373+
foreach (var category in categories)
1374+
{
1375+
CollectSitemapUrls(category, routeFileMap, gitRoot, urls);
1376+
}
1377+
1378+
var sitemapPath = Path.Combine(outputDir, "sitemap.xml");
1379+
WriteSitemap(sitemapPath, urls);
1380+
1381+
var robotsPath = Path.Combine(outputDir, "robots.txt");
1382+
WriteRobotsTxt(robotsPath);
1383+
1384+
Console.WriteLine($"Generated sitemap.xml ({urls.Count} URLs) and robots.txt in: {outputDir}");
1385+
}
1386+
1387+
static void CollectSitemapUrls(ExampleNode node, Dictionary<string, string> routeFileMap, string gitRoot, List<(string Url, string Lastmod)> urls)
1388+
{
1389+
if (!string.IsNullOrEmpty(node.Path))
1390+
{
1391+
var route = node.Path.TrimStart('/');
1392+
var url = string.IsNullOrEmpty(route) ? BaseUrl + "/" : $"{BaseUrl}/{route}";
1393+
1394+
string lastmod = null;
1395+
if (gitRoot != null && routeFileMap.TryGetValue(route, out var filePath))
1396+
{
1397+
lastmod = GetGitLastModified(gitRoot, filePath);
1398+
}
1399+
1400+
urls.Add((url, lastmod));
1401+
}
1402+
1403+
if (node.Children != null)
1404+
{
1405+
foreach (var child in node.Children)
1406+
{
1407+
CollectSitemapUrls(child, routeFileMap, gitRoot, urls);
1408+
}
1409+
}
1410+
}
1411+
1412+
static Dictionary<string, string> BuildRouteFileMap(string pagesPath)
1413+
{
1414+
var map = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
1415+
foreach (var file in Directory.GetFiles(pagesPath, "*.razor", SearchOption.AllDirectories))
1416+
{
1417+
var content = File.ReadAllText(file);
1418+
var match = Regex.Match(content, @"@page\s+""(/[^""]*)""\s*$", RegexOptions.Multiline);
1419+
if (match.Success)
1420+
map[match.Groups[1].Value.TrimStart('/')] = file;
1421+
}
1422+
return map;
1423+
}
1424+
1425+
static string FindGitRoot(string startDir)
1426+
{
1427+
var dir = Path.GetFullPath(startDir);
1428+
while (dir != null)
1429+
{
1430+
if (Directory.Exists(Path.Combine(dir, ".git")))
1431+
return dir;
1432+
dir = Path.GetDirectoryName(dir);
1433+
}
1434+
return null;
1435+
}
1436+
1437+
static string GetGitLastModified(string repoRoot, string filePath)
1438+
{
1439+
var relativePath = Path.GetRelativePath(repoRoot, filePath);
1440+
try
1441+
{
1442+
var psi = new ProcessStartInfo
1443+
{
1444+
FileName = "git",
1445+
Arguments = $"log --format=%aI -1 -- \"{relativePath}\"",
1446+
WorkingDirectory = repoRoot,
1447+
RedirectStandardOutput = true,
1448+
RedirectStandardError = true,
1449+
UseShellExecute = false,
1450+
CreateNoWindow = true
1451+
};
1452+
1453+
using var process = Process.Start(psi);
1454+
var output = process.StandardOutput.ReadToEnd().Trim();
1455+
process.WaitForExit();
1456+
1457+
if (process.ExitCode == 0 && !string.IsNullOrEmpty(output))
1458+
{
1459+
if (DateTimeOffset.TryParse(output, out var dto))
1460+
return dto.ToString("yyyy-MM-dd");
1461+
}
1462+
}
1463+
catch
1464+
{
1465+
// git not available — skip lastmod
1466+
}
1467+
1468+
return null;
1469+
}
1470+
1471+
static void WriteSitemap(string filePath, List<(string Url, string Lastmod)> urls)
1472+
{
1473+
var sb = new StringBuilder();
1474+
sb.AppendLine("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
1475+
sb.AppendLine("<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">");
1476+
1477+
foreach (var (url, lastmod) in urls)
1478+
{
1479+
sb.AppendLine(" <url>");
1480+
sb.AppendLine($" <loc>{url}</loc>");
1481+
if (!string.IsNullOrEmpty(lastmod))
1482+
sb.AppendLine($" <lastmod>{lastmod}</lastmod>");
1483+
sb.AppendLine(" </url>");
1484+
}
1485+
1486+
sb.AppendLine("</urlset>");
1487+
File.WriteAllText(filePath, sb.ToString(), new UTF8Encoding(false));
1488+
}
1489+
1490+
static void WriteRobotsTxt(string filePath)
1491+
{
1492+
var sb = new StringBuilder();
1493+
sb.AppendLine("User-agent: *");
1494+
sb.AppendLine($"Sitemap: {BaseUrl}/sitemap.xml");
1495+
sb.AppendLine("Disallow: /llms.txt");
1496+
sb.AppendLine("Disallow: /*.md$");
1497+
File.WriteAllText(filePath, sb.ToString(), new UTF8Encoding(false));
1498+
}
13611499
}

0 commit comments

Comments
 (0)