diff --git a/app/README.md b/app/README.md index a8260cd4..2c52d31b 100644 --- a/app/README.md +++ b/app/README.md @@ -178,6 +178,10 @@ Optionally, configure your code editor to auto run these tools on file save. Mos +## Sitemap + +A dynamically generated sitemap is available for the application at http://localhost:3001/sitemap.xml. The sitemap helps search engines index the content of your site more efficiently and can also be used for tools such as accessibility scans. This sitemap is automatically updated to reflect the current application routes. + ## Other topics - [Internationalization](../docs/app/internationalization.md) diff --git a/app/src/app/[locale]/health/page.tsx b/app/src/app/[locale]/health/page.tsx index adbd9f30..32840f27 100644 --- a/app/src/app/[locale]/health/page.tsx +++ b/app/src/app/[locale]/health/page.tsx @@ -1,3 +1,13 @@ +import { useTranslations } from "next-intl"; + export default function Page() { - return <>healthy; + const t = useTranslations("health"); + return ( + <> + + {t("title")} + +
{t("healthy")}
+ + ); } diff --git a/app/src/app/sitemap.ts b/app/src/app/sitemap.ts new file mode 100644 index 00000000..f689992a --- /dev/null +++ b/app/src/app/sitemap.ts @@ -0,0 +1,17 @@ +import { MetadataRoute } from "next"; + +import { getNextRoutes } from "../utils/getRoutes"; + +export default function sitemap(): MetadataRoute.Sitemap { + const routes = getNextRoutes("./src/app"); + + const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"; + const sitemap: MetadataRoute.Sitemap = routes.map((route) => ({ + url: `${baseUrl}${route || ""}`, + lastModified: new Date().toISOString(), + changeFrequency: "weekly", + priority: 0.5, + })); + + return sitemap; +} diff --git a/app/src/i18n/messages/en-US/index.ts b/app/src/i18n/messages/en-US/index.ts index 5e856e07..9d9bcac1 100644 --- a/app/src/i18n/messages/en-US/index.ts +++ b/app/src/i18n/messages/en-US/index.ts @@ -26,4 +26,8 @@ export const messages = { formatting: "The template includes an internationalization library with basic formatters built-in. Such as numbers: { amount, number, currency }, and dates: { isoDate, date, long}.", }, + health: { + title: "Health Check", + healthy: "healthy", + }, }; diff --git a/app/src/middleware.ts b/app/src/middleware.ts index 18d43c06..6f0ea8a6 100644 --- a/app/src/middleware.ts +++ b/app/src/middleware.ts @@ -11,7 +11,7 @@ import { defaultLocale, locales } from "./i18n/config"; // Don't run middleware on API routes or Next.js build output export const config = { - matcher: ["/((?!api|_next|.*\\..*).*)"], + matcher: ["/((?!api|_next|sitemap|.*\\..*).*)"], }; /** diff --git a/app/src/utils/getRoutes.ts b/app/src/utils/getRoutes.ts new file mode 100644 index 00000000..eba82551 --- /dev/null +++ b/app/src/utils/getRoutes.ts @@ -0,0 +1,41 @@ +import fs from "node:fs"; +import path from "node:path"; + +// Helper function to list all paths in a directory recursively +export function listPaths(dir: string): string[] { + let fileList: string[] = []; + const files = fs.readdirSync(dir); + files.forEach((file) => { + const filePath = path.join(dir, file); + if (fs.statSync(filePath).isDirectory()) { + fileList = fileList.concat(listPaths(filePath)); + } else { + fileList.push(filePath); + } + }); + return fileList; +} + +// Function to get the Next.js routes +export function getNextRoutes(src: string): string[] { + // Get all paths from the `app` directory + const appPaths = listPaths(src).filter((file) => file.endsWith("page.tsx")); + + // Extract the route name for each `page.tsx` file + // Basically anything between [locale] and /page.tsx is extracted, + // which lets us get nested routes such as /newsletter/unsubscribe + const appRoutes = appPaths.map((filePath) => { + const relativePath = path.relative(src, filePath); + const route = relativePath + ? "/" + + relativePath + // for any additional overrides add more replace here... + .replace("/page.tsx", "") + .replace(/\[locale\]/g, "") + .replace(/\\/g, "/") + : "/"; + return route.replace(/\/\//g, "/"); + }); + + return appRoutes; +}