Next.js (App Router) clone of negindentalclinic.ir, wired to the Meshkee Website API (blog, contact form, favicon), with on-page SEO and a scroll-reveal animation sequence on the homepage. See context.md for full project context. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
80 lines
2.8 KiB
JavaScript
80 lines
2.8 KiB
JavaScript
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { NextResponse } from "next/server";
|
|
import { WEBSITE_DOMAIN } from "@/lib/meshkee";
|
|
|
|
// Published for the Meshkee business dashboard's "Refresh" sitemap button —
|
|
// see AI_PROMPT.md ("What the website must publish"). Meshkee's own nginx
|
|
// proxy serves the actual /sitemap.xml (and the CMS-driven child sitemaps
|
|
// like /sitemap-blogs.xml) from the API; this route only lists this app's
|
|
// *static* public pages so the dashboard can fold them into the sitemap
|
|
// index. Do not add app/sitemap.ts / app/robots.ts — those paths are owned
|
|
// by the Meshkee proxy once this site is live on a Meshkee-managed domain.
|
|
|
|
export const dynamic = "force-static";
|
|
|
|
// Route segments that never map to a crawlable public page: dynamic detail
|
|
// pages (CMS-managed — Meshkee generates their own sitemap entries), and
|
|
// this "meshkee/*" integration namespace itself.
|
|
const EXCLUDE_SEGMENT = (segment) =>
|
|
segment.startsWith("[") || segment === "meshkee" || segment.startsWith("_");
|
|
|
|
// Per-path SEO weight. Anything not listed falls back to a sane default.
|
|
const PAGE_META = {
|
|
"/": { changefreq: "daily", priority: 1.0 },
|
|
"/services": { changefreq: "weekly", priority: 0.8 },
|
|
"/about-us": { changefreq: "monthly", priority: 0.6 },
|
|
"/blog": { changefreq: "daily", priority: 0.7 },
|
|
"/contact-us": { changefreq: "monthly", priority: 0.5 },
|
|
"/virtual-tour": { changefreq: "yearly", priority: 0.4 },
|
|
};
|
|
|
|
/** Scan app/ for `page.js` files and turn each into a public URL path. */
|
|
function scanStaticPages(dir, baseSegments = []) {
|
|
const pages = [];
|
|
let entries;
|
|
try {
|
|
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
} catch {
|
|
return pages;
|
|
}
|
|
|
|
const hasPage = entries.some(
|
|
(e) => e.isFile() && /^page\.(js|jsx|ts|tsx)$/.test(e.name)
|
|
);
|
|
if (hasPage && !baseSegments.some(EXCLUDE_SEGMENT)) {
|
|
const urlPath = "/" + baseSegments.join("/");
|
|
pages.push(urlPath === "/" ? "/" : urlPath.replace(/\/$/, ""));
|
|
}
|
|
|
|
for (const entry of entries) {
|
|
if (!entry.isDirectory()) continue;
|
|
// route groups "(name)" don't appear in the URL
|
|
const isGroup = entry.name.startsWith("(") && entry.name.endsWith(")");
|
|
const nextSegments = isGroup
|
|
? baseSegments
|
|
: [...baseSegments, entry.name];
|
|
if (EXCLUDE_SEGMENT(entry.name)) continue;
|
|
pages.push(...scanStaticPages(path.join(dir, entry.name), nextSegments));
|
|
}
|
|
|
|
return pages;
|
|
}
|
|
|
|
export async function GET() {
|
|
const appDir = path.join(process.cwd(), "app");
|
|
const paths = scanStaticPages(appDir).sort(
|
|
(a, b) => a.split("/").length - b.split("/").length || a.localeCompare(b)
|
|
);
|
|
|
|
const staticPages = paths.map((p) => ({
|
|
path: p,
|
|
...(PAGE_META[p] || { changefreq: "monthly", priority: 0.5 }),
|
|
}));
|
|
|
|
return NextResponse.json({
|
|
baseUrl: `https://${WEBSITE_DOMAIN}`,
|
|
staticPages,
|
|
});
|
|
}
|