Files
sase.tr/apps/web/scripts/prerender.mjs
Sase Dev 7194393e6d feat: SEO altyapısı — pre-render, meta tags, blog posts, robots/sitemap
- vite-plugin-prerender yerine Playwright tabanlı post-build pre-render scripti
  (apps/web/scripts/prerender.mjs): 10 public route statik HTML'e dönüştürülüyor
- usePageMeta hook: her public route'da özgün title/description/canonical/OG tags
- index.html: favicon.svg, canonical, OG/Twitter Card, geliştirilmiş title+desc
- apps/web/public/: robots.txt, sitemap.xml (10 URL), favicon.svg
- nginx HSTS header eklendi (Strict-Transport-Security)
- Blog post sayfaları: blog_/$slug.tsx (4 tam yazı, 600-800 kelime)
- blog.tsx: post kartlarına Link eklendi
- Homepage: Schema.org JSON-LD (Organization + SoftwareApplication)
- Footer: placeholder href="#" sosyal linkler kaldırıldı
- deploy.sh + deploy.yml: playwright install chromium + prerender adımı

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 14:15:35 +00:00

101 lines
2.8 KiB
JavaScript

/**
* Post-build pre-renderer for sase.tr public pages.
*
* Starts a local vite preview server, visits each public route with
* headless Chromium, and saves the fully-rendered HTML to dist/.
* Crawlers that don't execute JavaScript receive complete HTML.
*
* Usage: node scripts/prerender.mjs
* Requires: dist/ to already exist (run `vite build` first)
*/
import { chromium } from "playwright";
import { preview } from "vite";
import { writeFileSync, mkdirSync, existsSync } from "fs";
import { join, dirname } from "path";
import { fileURLToPath } from "url";
const __dirname = dirname(fileURLToPath(import.meta.url));
const ROOT = join(__dirname, "..");
const DIST = join(ROOT, "dist");
const PORT = 4174;
/** Public routes to pre-render. Dashboard/auth routes are excluded — they require login. */
const ROUTES = [
"/",
"/pricing",
"/about",
"/blog",
"/blog/vin-nedir-nasil-okunur",
"/blog/orijinal-mi-muadil-mi",
"/blog/dijital-donusum",
"/blog/dogru-parcayi-bulun",
"/contact",
"/demo",
];
async function prerender() {
if (!existsSync(DIST)) {
console.error("❌ dist/ not found — run `vite build` first.");
process.exit(1);
}
console.log("🔍 Starting pre-render...\n");
// Start vite preview server (serves from dist/)
const server = await preview({
root: ROOT,
preview: { port: PORT, strictPort: false },
logLevel: "silent",
});
const browser = await chromium.launch({ args: ["--no-sandbox"] });
try {
for (const route of ROUTES) {
const page = await browser.newPage();
// Block all /api/ requests so auth resolves to unauthenticated immediately.
// Without this the page hangs waiting for session checks.
await page.route("**/api/**", (r) =>
r.fulfill({
status: 401,
contentType: "application/json",
body: JSON.stringify({ success: false, error: { code: "UNAUTHORIZED" } }),
}),
);
await page.goto(`http://localhost:${PORT}${route}`, {
waitUntil: "networkidle",
timeout: 30_000,
});
// Extra settle time for any deferred renders (animations, etc.)
await page.waitForTimeout(300);
const html = await page.content();
if (route === "/") {
writeFileSync(join(DIST, "index.html"), html, "utf-8");
} else {
const dir = join(DIST, route);
mkdirSync(dir, { recursive: true });
writeFileSync(join(dir, "index.html"), html, "utf-8");
}
await page.close();
console.log(`${route}`);
}
} finally {
await browser.close();
await server.close();
}
console.log("\n✅ Pre-render complete — all routes saved to dist/\n");
}
prerender().catch((err) => {
console.error("❌ Pre-render failed:", err.message);
process.exit(1);
});