Initial commit: Negin Dental Clinic website
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>
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
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,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user