{"id":77642,"date":"2026-07-09T02:06:11","date_gmt":"2026-07-09T02:06:11","guid":{"rendered":"https:\/\/www.devopsschool.com\/blog\/?p=77642"},"modified":"2026-07-09T02:21:31","modified_gmt":"2026-07-09T02:21:31","slug":"the-complete-technology-stack-for-enterprise-grade-static-website-development","status":"publish","type":"post","link":"https:\/\/www.devopsschool.com\/blog\/the-complete-technology-stack-for-enterprise-grade-static-website-development\/","title":{"rendered":"The Ultimate Tech Stack Blueprint for Building Large-Scale Static Websites with Astro"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">Final recommended stack<\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">Astro <span class=\"hljs-number\">7<\/span>\n+ TypeScript\n+ pnpm\n+ Node.js <span class=\"hljs-number\">24<\/span> LTS\n+ Tailwind CSS v4\n+ daisyUI v5\n+ Alpine.js\n+ Pagefind\n+ Astro Content Collections\n+ Markdown \/ MDX \/ <span class=\"hljs-built_in\">JSON<\/span> \/ YAML\n+ Zod validation\n+ @astrojs\/sitemap\n+ @astrojs\/mdx\n+ Astro Image \/ Picture\n+ Cheerio + Turndown + gray-matter <span class=\"hljs-keyword\">for<\/span> migration\n+ Playwright + axe-core + Lighthouse CI + HTMLHint <span class=\"hljs-keyword\">for<\/span> QA\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">That is the stack I would choose over the Bootstrap variants <strong>unless your existing HTML is already heavily Bootstrap-based<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Your best stack should be:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Layer<\/th><th>Final choice<\/th><\/tr><\/thead><tbody><tr><td>Static site generator<\/td><td><strong>Astro<\/strong><\/td><\/tr><tr><td>Output<\/td><td><strong>Static HTML\/CSS\/JS<\/strong><\/td><\/tr><tr><td>Runtime JavaScript<\/td><td><strong>Minimal islands + Alpine.js only where needed<\/strong><\/td><\/tr><tr><td>CSS foundation<\/td><td><strong>Tailwind CSS v4<\/strong><\/td><\/tr><tr><td>Component class system<\/td><td><strong>daisyUI v5<\/strong><\/td><\/tr><tr><td>Interactive UI<\/td><td><strong>Alpine.js<\/strong><\/td><\/tr><tr><td>Search<\/td><td><strong>Pagefind<\/strong><\/td><\/tr><tr><td>Content<\/td><td><strong>Astro Content Collections<\/strong><\/td><\/tr><tr><td>Content formats<\/td><td><strong>Markdown, MDX, JSON, YAML<\/strong><\/td><\/tr><tr><td>Validation<\/td><td><strong>Zod schemas<\/strong><\/td><\/tr><tr><td>Pagination<\/td><td><strong>Astro static pagination<\/strong><\/td><\/tr><tr><td>SEO<\/td><td><strong>Shared SEO component + frontmatter + JSON-LD<\/strong><\/td><\/tr><tr><td>Breadcrumbs<\/td><td><strong>Generated from taxonomy\/category hierarchy<\/strong><\/td><\/tr><tr><td>Ads<\/td><td><strong>Reusable Astro <code>AdSlot<\/code> component<\/strong><\/td><\/tr><tr><td>Migration tooling<\/td><td><strong>Cheerio, Turndown, gray-matter, custom scripts<\/strong><\/td><\/tr><tr><td>QA<\/td><td><strong>Astro check, HTMLHint, Playwright, axe-core, Lighthouse CI<\/strong><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Astro is a strong fit because it is built for content-driven sites, produces fast static HTML, and only ships JavaScript where you explicitly need interactivity. Its island architecture lets most of the page stay static HTML while only small components become interactive. (<a href=\"https:\/\/docs.astro.build\/en\/concepts\/islands\/?utm_source=chatgpt.com\">Astro Documentation<\/a>) Astro can prerender pages as static output, builds to a deploy-ready <code>dist\/<\/code> folder, and supports static pagination for generated routes. (<a href=\"https:\/\/docs.astro.build\/en\/reference\/configuration-reference\/?utm_source=chatgpt.com\">Astro Documentation<\/a>)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">As of July 9, 2026, Astro 7 is current, and Astro 7 introduced Vite 8, the Rust compiler, advanced routing, a background dev server, and structured logging. (<a href=\"https:\/\/www.npmjs.com\/package\/astro?utm_source=chatgpt.com\">npm<\/a>) If you are risk-averse, start the migration pilot on Astro 6.4 and upgrade after the first successful batch. For a new build, I would start with Astro 7 after a small smoke test.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">My strongest recommendation<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Use:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">Astro<\/span> + <span class=\"hljs-selector-tag\">Tailwind<\/span> <span class=\"hljs-selector-tag\">CSS<\/span> + <span class=\"hljs-selector-tag\">daisyUI<\/span> + <span class=\"hljs-selector-tag\">Alpine<\/span><span class=\"hljs-selector-class\">.js<\/span> + <span class=\"hljs-selector-tag\">Pagefind<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Do <strong>not<\/strong> use:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">Astro + Bootstrap + Tailwind + Flowbite + daisyUI\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">That becomes too heavy and inconsistent.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Also avoid using both <strong>Flowbite and daisyUI<\/strong> as primary component systems. Pick one.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">My choice for your use case is:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">Tailwind<\/span> <span class=\"hljs-selector-tag\">CSS<\/span> + <span class=\"hljs-selector-tag\">daisyUI<\/span> + <span class=\"hljs-selector-tag\">Alpine<\/span><span class=\"hljs-selector-class\">.js<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Why? Because your site sounds content-heavy, listing-heavy, category-heavy, SEO-heavy, and theme-heavy. daisyUI gives you semantic component classes and theme support on top of Tailwind, while Alpine.js gives you lightweight behavior for dropdowns, mobile menus, accordions, filter panels, tabs, modals, and small stateful UI. daisyUI v5 is a Tailwind component library with built-in themes and custom theme support. (<a href=\"https:\/\/daisyui.com\/?utm_source=chatgpt.com\">daisyUI<\/a>) Alpine.js is designed for adding minimal interactivity directly in HTML-like markup. (<a href=\"https:\/\/alpinejs.dev\/?utm_source=chatgpt.com\">Alpine.js<\/a>)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Flowbite is also good, especially if you want many ready-made Tailwind components with JavaScript\/data-attribute behavior. It has Astro integration and interactive UI components. (<a href=\"https:\/\/flowbite.com\/docs\/getting-started\/astro\/?utm_source=chatgpt.com\">Flowbite<\/a>) But for a long-lived custom MyMedicPlus design system, I would rather use <strong>daisyUI for component styling<\/strong> and <strong>Alpine.js for behavior<\/strong>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">When Bootstrap is still the better choice<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">There is one case where I would choose Bootstrap:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">Astro<\/span> + <span class=\"hljs-selector-tag\">Bootstrap<\/span> 5<span class=\"hljs-selector-class\">.3<\/span> + <span class=\"hljs-selector-tag\">custom<\/span> <span class=\"hljs-selector-tag\">SCSS<\/span> + <span class=\"hljs-selector-tag\">Pagefind<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Choose this only if <strong>60\u201370% of your existing HTML pages already use Bootstrap classes<\/strong> or your team is much faster with Bootstrap than Tailwind.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Bootstrap 5.3 has mature Sass customization through variables, maps, mixins, functions, color options, and theming. (<a href=\"https:\/\/getbootstrap.com\/docs\/5.3\/customize\/sass\/?utm_source=chatgpt.com\">Bootstrap<\/a>) So for a low-risk legacy migration, Bootstrap can be practical.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">But if you are building the <strong>future stack<\/strong>, I would choose Tailwind\/daisyUI.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Final decision table<\/h1>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Requirement<\/th><th>Recommended technology<\/th><th>Reason<\/th><\/tr><\/thead><tbody><tr><td>HTML-only final site<\/td><td>Astro static output<\/td><td>Clean static HTML\/CSS\/JS<\/td><\/tr><tr><td>Thousands of pages<\/td><td>Astro Content Collections<\/td><td>Typed content, build-time routes<\/td><\/tr><tr><td>Easy future pages<\/td><td>Markdown \/ MDX<\/td><td>Editors can create content without touching templates<\/td><\/tr><tr><td>Structured data pages<\/td><td>JSON \/ YAML<\/td><td>Good for categories, taxonomy, nav, ads, redirects<\/td><\/tr><tr><td>Common header\/menu\/footer<\/td><td>Astro layouts\/components<\/td><td>One source of truth<\/td><\/tr><tr><td>Mobile-first layout<\/td><td>Tailwind CSS<\/td><td>Strong responsive utility system<\/td><\/tr><tr><td>Custom MyMedicPlus theme<\/td><td>Tailwind theme + daisyUI themes + CSS variables<\/td><td>Brand tokens stay centralized<\/td><\/tr><tr><td>UI components<\/td><td>daisyUI<\/td><td>Buttons, cards, badges, alerts, dropdown styling<\/td><\/tr><tr><td>Mobile menu\/dropdowns<\/td><td>Alpine.js<\/td><td>Lightweight, no full frontend framework<\/td><\/tr><tr><td>FAQ accordions<\/td><td>Alpine.js or daisyUI collapse<\/td><td>Simple static-friendly interaction<\/td><\/tr><tr><td>Search<\/td><td>Pagefind<\/td><td>Fully static search index<\/td><\/tr><tr><td>Search filters<\/td><td>Pagefind filters + generated category URLs<\/td><td>Static-first and SEO-friendly<\/td><\/tr><tr><td>Sort<\/td><td>Static default sort + Pagefind sort for search results<\/td><td>Avoids fake sorting of only visible page<\/td><\/tr><tr><td>Pagination<\/td><td>Astro <code>paginate()<\/code><\/td><td>Build-time static pagination<\/td><\/tr><tr><td>Breadcrumbs<\/td><td>Astro component + taxonomy data + JSON-LD<\/td><td>SEO and UX<\/td><\/tr><tr><td>SEO<\/td><td>Shared <code>SEO.astro<\/code> component + frontmatter<\/td><td>Consistent metadata<\/td><\/tr><tr><td>Schema markup<\/td><td>Reusable JSON-LD component<\/td><td>Breadcrumb, Article, FAQ, CollectionPage<\/td><\/tr><tr><td>Ads<\/td><td><code>AdSlot.astro<\/code> + frontmatter\/config<\/td><td>Easy enable\/disable<\/td><\/tr><tr><td>Legacy HTML migration<\/td><td>Cheerio + Turndown + gray-matter<\/td><td>Parse, clean, convert, add frontmatter<\/td><\/tr><tr><td>Validation<\/td><td>Zod<\/td><td>Catch bad\/missing fields before build<\/td><\/tr><tr><td>Accessibility testing<\/td><td>Playwright + axe-core<\/td><td>Catch common a11y issues<\/td><\/tr><tr><td>Performance\/SEO CI<\/td><td>Lighthouse CI<\/td><td>Prevent regressions<\/td><\/tr><tr><td>HTML quality<\/td><td>HTMLHint<\/td><td>Static HTML checks<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">How the listing-page article maps to a static site<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">The article you referenced lists features like search, filters, sorting, pagination, result count, row actions, bulk actions, export, loading state, empty state, error state, mobile responsiveness, URL persistence, and server-side handling. (<a href=\"https:\/\/www.rajeshkumar.xyz\/blog\/plan-your-web-applications-design-mandatory-listing-page-features-for-web-applications\/\">Rajesh Kumar<\/a>)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For a <strong>public static site<\/strong>, you should reinterpret some of those features.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Public static listing page standard<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">For MyMedicPlus or a similar large content site, I would define every major listing\/category page like this:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Feature<\/th><th>Static-site implementation<\/th><\/tr><\/thead><tbody><tr><td>Breadcrumb<\/td><td><code>Breadcrumbs.astro<\/code> from taxonomy tree<\/td><\/tr><tr><td>H1 title<\/td><td>Page frontmatter or category data<\/td><\/tr><tr><td>SEO intro paragraph<\/td><td>Markdown\/MDX\/category description<\/td><\/tr><tr><td>Search box<\/td><td>Pagefind search<\/td><\/tr><tr><td>Filter panel<\/td><td>Static category\/tag\/location pages + Pagefind metadata filters<\/td><\/tr><tr><td>Sort dropdown<\/td><td>Build-time sort for listings; Pagefind sort for search results<\/td><\/tr><tr><td>Result count<\/td><td>Build-time count from content collection<\/td><\/tr><tr><td>Listing cards<\/td><td><code>ListingCard.astro<\/code><\/td><\/tr><tr><td>Pagination<\/td><td>Astro <code>paginate()<\/code><\/td><\/tr><tr><td>Items per page<\/td><td>Fixed SEO-safe page size, usually 12, 18, or 24<\/td><\/tr><tr><td>Optional ad block<\/td><td><code>AdSlot.astro<\/code> controlled by frontmatter\/config<\/td><\/tr><tr><td>Empty state<\/td><td><code>EmptyState.astro<\/code><\/td><\/tr><tr><td>Related categories<\/td><td>Generated from taxonomy\/category map<\/td><\/tr><tr><td>FAQ section<\/td><td>Visible content section, optional FAQ schema<\/td><\/tr><tr><td>Schema markup<\/td><td>JSON-LD component<\/td><\/tr><tr><td>Mobile-first layout<\/td><td>Tailwind responsive cards<\/td><\/tr><tr><td>URL query persistence<\/td><td>Search\/filter UI uses <code>URLSearchParams<\/code><\/td><\/tr><tr><td>Search indexing<\/td><td>Pagefind after Astro build<\/td><\/tr><tr><td>Sitemap inclusion<\/td><td><code>@astrojs\/sitemap<\/code><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">This is the correct static version of the article\u2019s listing-page standard.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Important correction: not every app feature belongs in static HTML<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">The article includes admin-style features such as:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">row actions\nbulk actions\n<span class=\"hljs-keyword\">delete<\/span>\npermission-based actions\nserver-side search\/filter\/sort\/pagination\ncreate button\n<span class=\"hljs-keyword\">export<\/span>\nrefresh\nconfirmation modal\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Those are correct for <strong>admin applications<\/strong>, but not always for <strong>public static websites<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For your static site, the equivalent is:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>App listing feature<\/th><th>Static-site equivalent<\/th><\/tr><\/thead><tbody><tr><td>Server-side pagination<\/td><td>Build-time pagination<\/td><\/tr><tr><td>Server-side search<\/td><td>Static Pagefind index<\/td><\/tr><tr><td>Server-side filters<\/td><td>Generated category\/tag\/location pages<\/td><\/tr><tr><td>URL query persistence<\/td><td>Client-side query params for search UI<\/td><\/tr><tr><td>Permissions<\/td><td>Not applicable in public static HTML<\/td><\/tr><tr><td>Bulk actions<\/td><td>Not applicable unless separate admin tool<\/td><\/tr><tr><td>Row delete\/edit<\/td><td>Not applicable unless separate CMS\/admin app<\/td><\/tr><tr><td>Export<\/td><td>Optional static download or generated CSV\/JSON<\/td><\/tr><tr><td>Refresh<\/td><td>Rebuild\/redeploy pipeline<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The big warning: <strong>do not implement \u201csecure permissions\u201d in client-side static HTML.<\/strong> If you need create\/edit\/delete\/approval workflows, build that as a separate authenticated admin\/CMS system and publish the output as static HTML.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Recommended architecture<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Use Astro as the publishing engine.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">Content files + data files\n        \u2193\nAstro content collections\n        \u2193\nTyped schemas <span class=\"hljs-keyword\">and<\/span> validation\n        \u2193\nLayouts <span class=\"hljs-keyword\">and<\/span> reusable components\n        \u2193\n<span class=\"hljs-keyword\">Static<\/span> category\/listing\/detail pages\n        \u2193\nPagefind <span class=\"hljs-keyword\">static<\/span> search index\n        \u2193\ndist\/ <span class=\"hljs-keyword\">static<\/span> website\n        \u2193\nCDN \/ <span class=\"hljs-keyword\">static<\/span> hosting\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Astro Content Collections are a strong fit here because they support Markdown, MDX, JSON, YAML, TOML, schemas, and build-time page generation. Astro\u2019s docs also describe content collections as useful for thousands or tens of thousands of pages when content is loaded and queried at build time. (<a href=\"https:\/\/docs.astro.build\/en\/guides\/content-collections\/\">Astro Documentation<\/a>)<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Suggested project structure<\/h1>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">mymedicplus-site\/\n  astro.config.mjs\n  package.json\n  tsconfig.json\n  src\/\n    content.config.ts\n\n    content\/\n      pages\/\n      articles\/\n      conditions\/\n      treatments\/\n      hospitals\/\n      doctors\/\n      categories\/\n\n    data\/\n      nav.yaml\n      taxonomy.yaml\n      redirects.csv\n      ads.yaml\n      authors.yaml\n      reviewers.yaml\n\n    layouts\/\n      BaseLayout.astro\n      ContentLayout.astro\n      ListingLayout.astro\n      CategoryLayout.astro\n\n    components\/\n      seo\/\n        SEO.astro\n        JsonLd.astro\n\n      nav\/\n        Header.astro\n        Footer.astro\n        Breadcrumbs.astro\n        MobileMenu.astro\n\n      listing\/\n        ListingShell.astro\n        SearchBox.astro\n        FilterPanel.astro\n        SortSelect.astro\n        ResultCount.astro\n        ListingCard.astro\n        Pagination.astro\n        EmptyState.astro\n        RelatedCategories.astro\n        FAQ.astro\n\n      ads\/\n        AdSlot.astro\n\n      ui\/\n        Button.astro\n        Card.astro\n        Badge.astro\n        Alert.astro\n        Modal.astro\n        Accordion.astro\n\n    pages\/\n      index.astro\n      search.astro\n      categories\/&#91;...slug].astro\n      articles\/&#91;...slug].astro\n      conditions\/&#91;...slug].astro\n      treatments\/&#91;...slug].astro\n      hospitals\/&#91;...slug].astro\n      doctors\/&#91;...slug].astro\n\n  scripts\/\n    migrate\/\n      inventory-html.ts\n      extract-html.ts\n      convert-to-mdx.ts\n      classify-pages.ts\n      validate-content.ts\n      generate-redirects.ts\n\n  <span class=\"hljs-keyword\">public<\/span>\/\n    assets\/\n    images\/\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Content format recommendation<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Use different formats for different kinds of content.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Content type<\/th><th>Best format<\/th><\/tr><\/thead><tbody><tr><td>Long articles<\/td><td>Markdown or MDX<\/td><\/tr><tr><td>Medical condition pages<\/td><td>MDX<\/td><\/tr><tr><td>Treatment pages<\/td><td>MDX<\/td><\/tr><tr><td>Category landing pages<\/td><td>Markdown\/MDX with frontmatter<\/td><\/tr><tr><td>Hospital\/doctor directories<\/td><td>JSON\/YAML if highly structured; MDX if each has rich body content<\/td><\/tr><tr><td>Navigation<\/td><td>YAML<\/td><\/tr><tr><td>Taxonomy<\/td><td>YAML or JSON<\/td><\/tr><tr><td>Ads configuration<\/td><td>YAML<\/td><\/tr><tr><td>Redirect map<\/td><td>CSV or YAML<\/td><\/tr><tr><td>FAQ data<\/td><td>Frontmatter or embedded MDX component<\/td><\/tr><tr><td>Legacy pages during migration<\/td><td>MDX or cleaned HTML inside Astro layout<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">For thousands of unstructured pages, do <strong>not<\/strong> try to manually rewrite everything immediately. First convert them into a normalized \u201clegacy content\u201d format, then progressively improve the most important pages.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Example content schema<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">You should force every page to have required metadata. This is what stops the new site from becoming another unstructured mess.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ src\/content.config.ts<\/span>\n<span class=\"hljs-keyword\">import<\/span> { defineCollection } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"astro:content\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { glob, file } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"astro\/loaders\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { z } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"astro\/zod\"<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> pages = defineCollection({\n  <span class=\"hljs-attr\">loader<\/span>: glob({\n    <span class=\"hljs-attr\">base<\/span>: <span class=\"hljs-string\">\".\/src\/content\/pages\"<\/span>,\n    <span class=\"hljs-attr\">pattern<\/span>: <span class=\"hljs-string\">\"**\/*.{md,mdx}\"<\/span>,\n  }),\n  <span class=\"hljs-attr\">schema<\/span>: z.object({\n    <span class=\"hljs-attr\">title<\/span>: z.string(),\n    <span class=\"hljs-attr\">description<\/span>: z.string().max(<span class=\"hljs-number\">170<\/span>),\n    <span class=\"hljs-attr\">slug<\/span>: z.string().optional(),\n    <span class=\"hljs-attr\">legacyUrl<\/span>: z.string().optional(),\n    <span class=\"hljs-attr\">category<\/span>: z.string().optional(),\n    <span class=\"hljs-attr\">tags<\/span>: z.array(z.string()).default(&#91;]),\n    <span class=\"hljs-attr\">updatedAt<\/span>: z.coerce.date(),\n    <span class=\"hljs-attr\">reviewedBy<\/span>: z.string().optional(),\n    <span class=\"hljs-attr\">author<\/span>: z.string().optional(),\n    <span class=\"hljs-attr\">noindex<\/span>: z.boolean().default(<span class=\"hljs-literal\">false<\/span>),\n    <span class=\"hljs-attr\">ads<\/span>: z.boolean().default(<span class=\"hljs-literal\">true<\/span>),\n    <span class=\"hljs-attr\">faqs<\/span>: z\n      .array(\n        z.object({\n          <span class=\"hljs-attr\">q<\/span>: z.string(),\n          <span class=\"hljs-attr\">a<\/span>: z.string(),\n        })\n      )\n      .default(&#91;]),\n  }),\n});\n\n<span class=\"hljs-keyword\">const<\/span> taxonomy = defineCollection({\n  <span class=\"hljs-attr\">loader<\/span>: file(<span class=\"hljs-string\">\".\/src\/data\/taxonomy.yaml\"<\/span>),\n  <span class=\"hljs-attr\">schema<\/span>: z.object({\n    <span class=\"hljs-attr\">id<\/span>: z.string(),\n    <span class=\"hljs-attr\">title<\/span>: z.string(),\n    <span class=\"hljs-attr\">slug<\/span>: z.string(),\n    <span class=\"hljs-attr\">parent<\/span>: z.string().optional(),\n    <span class=\"hljs-attr\">description<\/span>: z.string().optional(),\n  }),\n});\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> collections = {\n  pages,\n  taxonomy,\n};\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Zod is a TypeScript-first validation library, and gray-matter can parse frontmatter from strings\/files during migration. (<a href=\"https:\/\/zod.dev\/?utm_source=chatgpt.com\">Zod<\/a>)<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Pagefind search strategy<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Use Pagefind for static search.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Pagefind is specifically built for fully static, low-bandwidth search at scale, with no hosting infrastructure required. It runs after your static site generator and creates a static search bundle inside the generated site. (<a href=\"https:\/\/pagefind.app\/?utm_source=chatgpt.com\">Pagefind<\/a>)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Your build should work like this:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">astro build\npagefind --site dist\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Use Pagefind attributes inside templates:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">main<\/span> <span class=\"hljs-attr\">data-pagefind-body<\/span>&gt;<\/span>\n  ...\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">main<\/span>&gt;<\/span>\n\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span> <span class=\"hljs-attr\">data-pagefind-filter<\/span>=<span class=\"hljs-string\">\"category\"<\/span>&gt;<\/span>Hospitals<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span> <span class=\"hljs-attr\">data-pagefind-filter<\/span>=<span class=\"hljs-string\">\"location\"<\/span>&gt;<\/span>India<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span> <span class=\"hljs-attr\">data-pagefind-sort<\/span>=<span class=\"hljs-string\">\"updated\"<\/span>&gt;<\/span>2026-07-01<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Pagefind supports <code>data-pagefind-body<\/code>, filters, sort values, and a JavaScript API for filters\/sorting. (<a href=\"https:\/\/pagefind.app\/docs\/indexing\/?utm_source=chatgpt.com\">Pagefind<\/a>)<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Search UX recommendation<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Have two types of search\/filter pages:<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">1. SEO category pages<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">These should be static and crawlable:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">\/hospitals\/\n<span class=\"hljs-regexp\">\/hospitals\/i<\/span>ndia\/\n<span class=\"hljs-regexp\">\/hospitals\/i<\/span>ndia\/delhi\/\n<span class=\"hljs-regexp\">\/treatments\/<\/span>cardiology\/\n<span class=\"hljs-regexp\">\/conditions\/<\/span>diabetes\/\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">These pages should have:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">H1\nintro text\nbreadcrumb\nlisting cards\n<span class=\"hljs-keyword\">static<\/span> pagination\nrelated categories\nFAQ\nschema\ncanonical URL\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\">2. Interactive search page<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Use Pagefind for user convenience:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">\/search\/?q=heart&amp;category=hospitals&amp;location=delhi\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">This page can support:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">search box\nfilters\nsort\nresult count\nreset filters\n<span class=\"hljs-keyword\">empty<\/span> state\nURL query persistence\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Do not rely only on JavaScript filters for SEO-important category pages. Important filter combinations should become real generated URLs.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Pagination strategy<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Use Astro static pagination for category\/listing pages. Astro\u2019s <code>paginate()<\/code> automatically generates URLs for each page in a paginated collection. (<a href=\"https:\/\/docs.astro.build\/en\/reference\/routing-reference\/?utm_source=chatgpt.com\">Astro Documentation<\/a>)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Recommended URL pattern:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">\/hospitals\/\n<span class=\"hljs-regexp\">\/hospitals\/<\/span><span class=\"hljs-number\">2<\/span>\/\n<span class=\"hljs-regexp\">\/hospitals\/<\/span><span class=\"hljs-number\">3<\/span>\/\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Avoid this for SEO-critical pages:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">\/hospitals?page=2\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">For items per page, I would <strong>not<\/strong> make it fully dynamic on public SEO pages. Use a fixed page size:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">12 cards per page for visual directories\n24 items per page for compact lists\n50 items per page for simple text directories\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">A dynamic <code>?per_page=12|24|48<\/code> creates duplicate\/canonical complexity. Keep that kind of behavior only on the <code>\/search\/<\/code> page.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Breadcrumb and schema strategy<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Generate breadcrumbs from taxonomy, not manually.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Example:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">Home &gt; Treatments &gt; Cardiology &gt; Heart Surgery\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Use:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">Breadcrumbs<\/span><span class=\"hljs-selector-class\">.astro<\/span>\n<span class=\"hljs-selector-tag\">JsonLd<\/span><span class=\"hljs-selector-class\">.astro<\/span>\n<span class=\"hljs-selector-tag\">taxonomy<\/span><span class=\"hljs-selector-class\">.yaml<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Google\u2019s breadcrumb structured data documentation says breadcrumbs indicate a page\u2019s position in the site hierarchy and help users understand and explore the site. (<a href=\"https:\/\/developers.google.com\/search\/docs\/appearance\/structured-data\/breadcrumb?utm_source=chatgpt.com\">Google for Developers<\/a>) Schema.org also defines <code>BreadcrumbList<\/code> for breadcrumb structures. (<a href=\"https:\/\/schema.org\/BreadcrumbList?utm_source=chatgpt.com\">Schema.org<\/a>)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Use JSON-LD for:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Page type<\/th><th>Schema<\/th><\/tr><\/thead><tbody><tr><td>Article<\/td><td><code>Article<\/code> or <code>BlogPosting<\/code><\/td><\/tr><tr><td>Medical content<\/td><td>Use appropriate medical schema carefully, only when accurate<\/td><\/tr><tr><td>FAQ section<\/td><td><code>FAQPage<\/code> only when FAQ is visible on page<\/td><\/tr><tr><td>Breadcrumbs<\/td><td><code>BreadcrumbList<\/code><\/td><\/tr><tr><td>Listing\/category page<\/td><td><code>CollectionPage<\/code> or <code>WebPage<\/code><\/td><\/tr><tr><td>Organization<\/td><td><code>Organization<\/code><\/td><\/tr><tr><td>Website search<\/td><td><code>WebSite<\/code> with search action, where appropriate<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Important: Google announced that FAQ rich results are no longer shown in Google Search as of May 2026, so do not treat FAQ schema as a guaranteed SEO-rich-result tactic. Keep FAQs because they help users and page quality. (<a href=\"https:\/\/developers.google.com\/search\/updates\/?utm_source=chatgpt.com\">Google for Developers<\/a>)<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">SEO stack<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Use one shared SEO component.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">SEO<\/span><span class=\"hljs-selector-class\">.astro<\/span>\n<span class=\"hljs-selector-tag\">JsonLd<\/span><span class=\"hljs-selector-class\">.astro<\/span>\n<span class=\"hljs-selector-tag\">Canonical<\/span><span class=\"hljs-selector-class\">.astro<\/span>\n<span class=\"hljs-selector-tag\">OpenGraph<\/span><span class=\"hljs-selector-class\">.astro<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Every page should have:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-16\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">title\nmeta description\ncanonical URL\nrobots setting\nOpen Graph title\nOpen Graph description\nOpen Graph image\nbreadcrumb <span class=\"hljs-built_in\">JSON<\/span>-LD\npage-specific schema\nlast updated date\nauthor\/reviewer where relevant\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Google recommends using canonical URLs consistently and supports <code>rel=\"canonical\"<\/code> as a way to indicate the preferred URL. (<a href=\"https:\/\/developers.google.com\/search\/docs\/crawling-indexing\/consolidate-duplicate-urls?utm_source=chatgpt.com\">Google for Developers<\/a>) The <code>@astrojs\/sitemap<\/code> integration can generate sitemap indexes and sitemap files from your site URL. (<a href=\"https:\/\/docs.astro.build\/en\/guides\/integrations-guide\/sitemap\/?utm_source=chatgpt.com\">Astro Documentation<\/a>)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Because MyMedicPlus sounds health-related, treat the site as <strong>YMYL<\/strong> content. Google\u2019s guidance around helpful, reliable, people-first content and quality-rater guidance applies especially strongly to topics where low-quality information could affect health, safety, or finances. (<a href=\"https:\/\/developers.google.com\/search\/docs\/fundamentals\/creating-helpful-content?utm_source=chatgpt.com\">Google for Developers<\/a>)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For medical pages, add:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">author\nmedical reviewer\nreviewed date\nupdated date\nsources\/references\ndisclaimer\nclear ownership\/about information\ncontact information\neditorial policy\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">That matters more than stuffing pages with schema.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">CSS and design-system recommendation<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Use Tailwind v4 with:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-17\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-keyword\">@tailwindcss<\/span>\/typography\n@tailwindcss\/forms\ndaisyUI\ncustom CSS variables\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-17\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Tailwind\u2019s official Astro installation now uses the Vite plugin, and the older <code>@astrojs\/tailwind<\/code> integration is deprecated for newer Tailwind usage. (<a href=\"https:\/\/tailwindcss.com\/docs\/installation\/framework-guides\/astro?utm_source=chatgpt.com\">Tailwind CSS<\/a>)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Use the Typography plugin for migrated article content, because it gives good default styling for raw Markdown or HTML-like content you do not fully control. (<a href=\"https:\/\/github.com\/tailwindlabs\/tailwindcss-typography?utm_source=chatgpt.com\">GitHub<\/a>) Use the Forms plugin for consistent form controls. (<a href=\"https:\/\/github.com\/tailwindlabs\/tailwindcss-forms?utm_source=chatgpt.com\">GitHub<\/a>)<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Very important Tailwind migration warning<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Tailwind scans source files as plain text and does not understand dynamically generated class names. It will not reliably detect code like:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-18\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-string\">`bg-<span class=\"hljs-subst\">${color}<\/span>-500`<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-18\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Tailwind recommends mapping dynamic values to complete class names instead. (<a href=\"https:\/\/tailwindcss.com\/docs\/detecting-classes-in-source-files?utm_source=chatgpt.com\">Tailwind CSS<\/a>)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So for migrated pages, avoid letting content generate arbitrary Tailwind classes. Use:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">content body \u2192 prose classes\ncomponents \u2192 controlled Tailwind classes\ntheme values \u2192 CSS variables\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">This keeps the design consistent and avoids broken production CSS.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Component library decision<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">Best choice<\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-19\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">Tailwind<\/span> + <span class=\"hljs-selector-tag\">daisyUI<\/span> + <span class=\"hljs-selector-tag\">Alpine<\/span><span class=\"hljs-selector-class\">.js<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-19\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Use daisyUI for:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">buttons\ncards\nbadges\nalerts\ndropdown styling\ntabs\naccordions\/collapse styling\nmodal styling\npagination styling\nform styling\ntheme tokens\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Use Alpine.js for:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">mobile menu\ndropdown open\/close\nfilter panel\naccordion behavior\nFAQ expand\/collapse\nmodal state\ntabs\nreset filters\nURL query persistence\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Alpine supports small reactive blocks using <code>x-data<\/code>, and its collapse plugin supports smooth show\/hide behavior with <code>x-show<\/code> and <code>x-collapse<\/code>. (<a href=\"https:\/\/alpinejs.dev\/directives\/data?utm_source=chatgpt.com\">Alpine.js<\/a>)<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Alternative<\/h2>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">Tailwind + Flowbite + minimal Alpine\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Use this if you want more prebuilt copy-paste components and are okay using Flowbite\u2019s JavaScript behavior.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Avoid<\/h2>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">Tailwind + Flowbite + daisyUI + Bootstrap\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">That is too much. You will end up debugging CSS specificity, duplicate components, inconsistent spacing, and design drift.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Listing-page component standard<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Create one reusable listing system.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-20\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">ListingLayout<\/span><span class=\"hljs-selector-class\">.astro<\/span>\n<span class=\"hljs-selector-tag\">ListingShell<\/span><span class=\"hljs-selector-class\">.astro<\/span>\n<span class=\"hljs-selector-tag\">SearchBox<\/span><span class=\"hljs-selector-class\">.astro<\/span>\n<span class=\"hljs-selector-tag\">FilterPanel<\/span><span class=\"hljs-selector-class\">.astro<\/span>\n<span class=\"hljs-selector-tag\">SortSelect<\/span><span class=\"hljs-selector-class\">.astro<\/span>\n<span class=\"hljs-selector-tag\">ResultCount<\/span><span class=\"hljs-selector-class\">.astro<\/span>\n<span class=\"hljs-selector-tag\">ListingCard<\/span><span class=\"hljs-selector-class\">.astro<\/span>\n<span class=\"hljs-selector-tag\">Pagination<\/span><span class=\"hljs-selector-class\">.astro<\/span>\n<span class=\"hljs-selector-tag\">EmptyState<\/span><span class=\"hljs-selector-class\">.astro<\/span>\n<span class=\"hljs-selector-tag\">RelatedCategories<\/span><span class=\"hljs-selector-class\">.astro<\/span>\n<span class=\"hljs-selector-tag\">FAQ<\/span><span class=\"hljs-selector-class\">.astro<\/span>\n<span class=\"hljs-selector-tag\">AdSlot<\/span><span class=\"hljs-selector-class\">.astro<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-20\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Standard category listing page<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Each category landing page should include:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-21\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-number\">1.<\/span> Breadcrumb\n<span class=\"hljs-number\">2.<\/span> H1 page title\n<span class=\"hljs-number\">3.<\/span> SEO intro paragraph\n<span class=\"hljs-number\">4.<\/span> Optional hero\/summary card\n<span class=\"hljs-number\">5.<\/span> Search box <span class=\"hljs-keyword\">or<\/span> link to search page\n<span class=\"hljs-number\">6.<\/span> Category\/type filters\n<span class=\"hljs-number\">7.<\/span> Sort dropdown, <span class=\"hljs-keyword\">if<\/span> useful\n<span class=\"hljs-number\">8.<\/span> Total result count\n<span class=\"hljs-number\">9.<\/span> Paginated cards\/listings\n<span class=\"hljs-number\">10.<\/span> Fixed items per page\n<span class=\"hljs-number\">11.<\/span> Reset filters button where filters exist\n<span class=\"hljs-number\">12.<\/span> <span class=\"hljs-keyword\">Empty<\/span> state\n<span class=\"hljs-number\">13.<\/span> Optional ad block\n<span class=\"hljs-number\">14.<\/span> Related categories\n<span class=\"hljs-number\">15.<\/span> FAQ section\n<span class=\"hljs-number\">16.<\/span> Schema markup\n<span class=\"hljs-number\">17.<\/span> Canonical URL\n<span class=\"hljs-number\">18.<\/span> Sitemap inclusion\n<span class=\"hljs-number\">19.<\/span> Mobile-first layout\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-21\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Recommended behavior<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Feature<\/th><th>Recommendation<\/th><\/tr><\/thead><tbody><tr><td>Search box<\/td><td>On <code>\/search\/<\/code>; optional compact search on category pages<\/td><\/tr><tr><td>Filters<\/td><td>Static URLs for SEO-important filters; Pagefind filters for interactive search<\/td><\/tr><tr><td>Sort<\/td><td>Default static sort by relevance\/name\/date; optional Pagefind sorting<\/td><\/tr><tr><td>Result count<\/td><td>Build-time count<\/td><\/tr><tr><td>Pagination<\/td><td>Static Astro pagination<\/td><\/tr><tr><td>Reset filters<\/td><td>Alpine\/vanilla JS<\/td><\/tr><tr><td>URL persistence<\/td><td>Use query params only for search UI<\/td><\/tr><tr><td>Empty state<\/td><td>Custom text based on category\/filter<\/td><\/tr><tr><td>Ads<\/td><td>Controlled by frontmatter<\/td><\/tr><tr><td>FAQ<\/td><td>Visible content, not hidden SEO bait<\/td><\/tr><tr><td>Schema<\/td><td>Generated, never manually copied page by page<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Example listing page data model<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">For category pages:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-22\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">id: hospitals-india\n<span class=\"hljs-attr\">title<\/span>: Hospitals <span class=\"hljs-keyword\">in<\/span> India\n<span class=\"hljs-attr\">slug<\/span>: <span class=\"hljs-regexp\">\/hospitals\/i<\/span>ndia\/\nparent: hospitals\n<span class=\"hljs-attr\">description<\/span>: Find hospitals <span class=\"hljs-keyword\">in<\/span> India by specialty, location, and treatment category.\nseoTitle: Hospitals <span class=\"hljs-keyword\">in<\/span> India | MyMedicPlus\n<span class=\"hljs-attr\">seoDescription<\/span>: Browse hospitals <span class=\"hljs-keyword\">in<\/span> India by specialty, city, and treatment category.\nfilters:\n  - specialty\n  - city\n  - treatment\n<span class=\"hljs-attr\">ads<\/span>: <span class=\"hljs-literal\">true<\/span>\n<span class=\"hljs-attr\">faqs<\/span>:\n  - q: How <span class=\"hljs-keyword\">do<\/span> I choose a hospital <span class=\"hljs-keyword\">in<\/span> India?\n    a: Compare specialty, accreditation, doctor availability, and patient support services.\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-22\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">For content pages:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-23\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">title: Heart Surgery <span class=\"hljs-keyword\">in<\/span> India\n<span class=\"hljs-attr\">description<\/span>: Learn about heart surgery options, hospital selection, recovery, and planning.\ncategory: cardiology\n<span class=\"hljs-attr\">tags<\/span>:\n  - heart surgery\n  - cardiology\n  - india\n<span class=\"hljs-attr\">updatedAt<\/span>: <span class=\"hljs-number\">2026<\/span><span class=\"hljs-number\">-07<\/span><span class=\"hljs-number\">-01<\/span>\n<span class=\"hljs-attr\">reviewedBy<\/span>: Dr Example\n<span class=\"hljs-attr\">ads<\/span>: <span class=\"hljs-literal\">true<\/span>\n<span class=\"hljs-attr\">legacyUrl<\/span>: <span class=\"hljs-regexp\">\/old\/<\/span>heart-surgery-india.html\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-23\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Migration strategy for thousands of unstructured HTML pages<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Do <strong>not<\/strong> manually migrate everything.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Use a staged migration.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Phase 1: Inventory<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Write a script that scans all old HTML files and extracts:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">old file path\nold URL\ntitle tag\nmeta description\ncanonical\nH1\nH2s\nmain content selector\ninternal links\nimages\nscripts\ninline styles\ntables\nforms\ndetected page type\nword count\nbroken\/missing metadata\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Use Cheerio for parsing. Cheerio is widely used for parsing and manipulating HTML\/XML in JavaScript and provides a jQuery-like API. (<a href=\"https:\/\/cheerio.js.org\/?utm_source=chatgpt.com\">Cheerio<\/a>)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Output:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-24\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">migration-inventory<\/span><span class=\"hljs-selector-class\">.csv<\/span>\n<span class=\"hljs-selector-tag\">redirect-map<\/span><span class=\"hljs-selector-class\">.csv<\/span>\n<span class=\"hljs-selector-tag\">content-quality-report<\/span><span class=\"hljs-selector-class\">.csv<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-24\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Phase 2: Classify pages<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Group old pages into page types:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">article\ncondition page\ntreatment page\nhospital page\ndoctor page\ncategory landing page\nFAQ page\nlisting page\nlegacy\/misc page\nthin\/duplicate page\nredirect-only page\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">This matters because a hospital page, a treatment article, and a category page should not share the same schema or layout.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Phase 3: Extract main content<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Strip old:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">header\nfooter\nnav\nold sidebars\nold ad blocks\ntracking scripts\ninline JavaScript\nduplicate breadcrumbs\noutdated menus\nlayout tables\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Keep:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">main article body\nheadings\nparagraphs\nlists\ntables\nimages\nimportant links\nFAQs\ncitations\/sources\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Phase 4: Convert to Markdown\/MDX<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Use Turndown for HTML-to-Markdown conversion. Turndown is a JavaScript HTML-to-Markdown converter. (<a href=\"https:\/\/github.com\/mixmark-io\/turndown?utm_source=chatgpt.com\">GitHub<\/a>)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Recommended rule:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Legacy content<\/th><th>Convert to<\/th><\/tr><\/thead><tbody><tr><td>Normal article body<\/td><td>Markdown<\/td><\/tr><tr><td>Article with custom components<\/td><td>MDX<\/td><\/tr><tr><td>Complex tables<\/td><td>Keep as HTML or convert to Astro component<\/td><\/tr><tr><td>Old forms<\/td><td>Rebuild as component<\/td><\/tr><tr><td>Old scripts\/widgets<\/td><td>Replace, do not blindly migrate<\/td><\/tr><tr><td>Layout wrappers<\/td><td>Delete<\/td><\/tr><tr><td>Old ad code<\/td><td>Replace with <code>AdSlot.astro<\/code><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Phase 5: Add frontmatter<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Use gray-matter to create frontmatter. (<a href=\"https:\/\/github.com\/jonschlinkert\/gray-matter?utm_source=chatgpt.com\">GitHub<\/a>)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Example generated file:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-25\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">---\ntitle: <span class=\"hljs-string\">\"Heart Surgery in India\"<\/span>\ndescription: <span class=\"hljs-string\">\"Learn about heart surgery in India, hospital selection, recovery, and treatment planning.\"<\/span>\nlegacyUrl: <span class=\"hljs-string\">\"\/old\/heart-surgery-india.html\"<\/span>\ncategory: <span class=\"hljs-string\">\"cardiology\"<\/span>\ntags:\n  - heart surgery\n  - india\nupdatedAt: <span class=\"hljs-string\">\"2026-07-01\"<\/span>\nads: <span class=\"hljs-keyword\">true<\/span>\n---\n\n<span class=\"hljs-comment\"># Heart Surgery in India<\/span>\n\nMigrated content here...\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-25\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Phase 6: Preserve URLs or create redirects<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">For pages with traffic\/backlinks, preserve the old URL where possible.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If URL must change, create a redirect map:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-26\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">from<\/span>,to,status\n\/old\/heart-surgery-india.html,\/treatments\/cardiology\/heart-surgery-india\/,<span class=\"hljs-number\">301<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-26\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Astro supports configured redirects, but static redirects ultimately depend on your hosting platform. Astro\u2019s runtime <code>Astro.redirect<\/code> is server-side and not available for static-only output, so static deployments should use hosting redirects or generated redirect files where supported. (<a href=\"https:\/\/docs.astro.build\/en\/guides\/routing\/?utm_source=chatgpt.com\">Astro Documentation<\/a>)<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Phase 7: Progressive cleanup<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Do not perfect all 1,000+ pages before launch.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Use this order:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-27\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-number\">1.<\/span> High-traffic pages\n<span class=\"hljs-number\">2.<\/span> High-value SEO pages\n<span class=\"hljs-number\">3.<\/span> Category landing pages\n<span class=\"hljs-number\">4.<\/span> Medical\/YMYL pages needing review\n<span class=\"hljs-number\">5.<\/span> Pages <span class=\"hljs-keyword\">with<\/span> backlinks\n<span class=\"hljs-number\">6.<\/span> Thin\/duplicate pages\n<span class=\"hljs-number\">7.<\/span> Low-value legacy pages\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-27\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">First make the pages render correctly inside Astro. Then improve the important ones.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Build and package setup<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Use Node.js 24 LTS and pnpm. Node\u2019s official release page lists v24.18.0 as the latest LTS line as of the current release information, and pnpm describes itself as a fast, disk-space-efficient package manager with workspace support. (<a href=\"https:\/\/nodejs.org\/en\/download?utm_source=chatgpt.com\">Node.js<\/a>)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Suggested install:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-28\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">pnpm<\/span> <span class=\"hljs-selector-tag\">create<\/span> <span class=\"hljs-selector-tag\">astro<\/span><span class=\"hljs-keyword\">@latest<\/span> mymedicplus-site\ncd mymedicplus-site\n\npnpm add alpinejs @alpinejs\/collapse @alpinejs\/focus @alpinejs\/persist\npnpm add lucide-astro\n\npnpm add -D @astrojs\/check @astrojs\/mdx @astrojs\/sitemap typescript\npnpm add -D tailwindcss @tailwindcss\/vite daisyui @tailwindcss\/typography @tailwindcss\/forms\npnpm add -D pagefind\npnpm add -D cheerio turndown gray-matter fast-glob js-yaml\npnpm add -D htmlhint @playwright\/test @axe-core\/playwright @lhci\/cli\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-28\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Suggested scripts:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-29\" data-shcb-language-name=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json\">{\n  <span class=\"hljs-attr\">\"scripts\"<\/span>: {\n    <span class=\"hljs-attr\">\"dev\"<\/span>: <span class=\"hljs-string\">\"astro dev\"<\/span>,\n    <span class=\"hljs-attr\">\"check\"<\/span>: <span class=\"hljs-string\">\"astro check\"<\/span>,\n    <span class=\"hljs-attr\">\"build\"<\/span>: <span class=\"hljs-string\">\"astro build &amp;&amp; pagefind --site dist\"<\/span>,\n    <span class=\"hljs-attr\">\"preview\"<\/span>: <span class=\"hljs-string\">\"astro build &amp;&amp; pagefind --site dist --serve\"<\/span>,\n    <span class=\"hljs-attr\">\"lint:html\"<\/span>: <span class=\"hljs-string\">\"htmlhint \\\"dist\/**\/*.html\\\"\"<\/span>,\n    <span class=\"hljs-attr\">\"test:a11y\"<\/span>: <span class=\"hljs-string\">\"playwright test\"<\/span>,\n    <span class=\"hljs-attr\">\"lhci\"<\/span>: <span class=\"hljs-string\">\"lhci autorun\"<\/span>\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-29\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JSON \/ JSON with Comments<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">json<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Pagefind is designed to run after your static site generator has produced the site. (<a href=\"https:\/\/pagefind.app\/docs\/?utm_source=chatgpt.com\">Pagefind<\/a>) Astro\u2019s preview command serves the built <code>dist\/<\/code> directory by default. (<a href=\"https:\/\/docs.astro.build\/en\/reference\/cli-reference\/?utm_source=chatgpt.com\">Astro Documentation<\/a>)<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Accessibility requirements<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Build this into the design system from day one.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For listing pages:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-30\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">search input has visible label\nfilters are keyboard accessible\nsort dropdown has label\npagination links are real links\ncurrent page is announced\ncards have clear heading hierarchy\nicon buttons have accessible labels\naccordions are keyboard usable\nmodals <span class=\"hljs-keyword\">return<\/span> focus correctly\nloading and no-results states are announced\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-30\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">WCAG 2.2 defines testable accessibility success criteria for web content. (<a href=\"https:\/\/www.w3.org\/TR\/WCAG22\/?utm_source=chatgpt.com\">W3C<\/a>) For sortable tables, WAI-ARIA\u2019s example recommends native table markup, <code>aria-sort<\/code> on the sorted column, and button text inside sortable headers. (<a href=\"https:\/\/www.w3.org\/WAI\/ARIA\/apg\/patterns\/table\/examples\/sortable-table\/?utm_source=chatgpt.com\">W3C<\/a>)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Use:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-31\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-keyword\">@axe-core<\/span>\/playwright\nPlaywright\nLighthouse CI\nmanual keyboard testing\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-31\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Playwright\u2019s accessibility testing guidance with axe can catch issues such as color contrast, unlabeled controls, and duplicate IDs. Lighthouse CI can run reports in pull requests and prevent performance, accessibility, and SEO regressions. (<a href=\"https:\/\/playwright.dev\/docs\/accessibility-testing?utm_source=chatgpt.com\">Playwright<\/a>)<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Quality gates before publishing<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Every build should check:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-32\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">Astro type\/content validation\nmissing title\nmissing meta description\nduplicate title\nduplicate canonical\nmissing H1\nmultiple H1s\nbroken internal links\nmissing image alt text\noversized images\ninvalid schema <span class=\"hljs-built_in\">JSON<\/span>\nmissing breadcrumb\nmissing sitemap entry\nPagefind index built\nHTMLHint passed\naccessibility smoke tests passed\nLighthouse budget passed\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-32\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">HTMLHint is a static analysis tool for HTML. (<a href=\"https:\/\/htmlhint.com\/?utm_source=chatgpt.com\">HTMLHint<\/a>)<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">What I would not do<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">I would not choose this:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">HTML5 + Tailwind + Alpine + Flowbite + daisyUI\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Too many UI systems.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I would not choose this either:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">Astro + Bootstrap + Tailwind\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Two CSS frameworks will fight each other.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I would not build thousands of static pages by copying old HTML into new templates manually.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I would not make all filters query-string-only if they matter for SEO.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I would not rely on FAQ schema as an SEO trick.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I would not put admin\/edit\/delete\/security behavior into static public HTML.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Best final stack summary<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Your final technology stack should be:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Area<\/th><th>Final choice<\/th><\/tr><\/thead><tbody><tr><td>Static generator<\/td><td><strong>Astro 7<\/strong><\/td><\/tr><tr><td>Package manager<\/td><td><strong>pnpm<\/strong><\/td><\/tr><tr><td>Runtime\/build<\/td><td><strong>Node.js 24 LTS<\/strong><\/td><\/tr><tr><td>Language<\/td><td><strong>TypeScript<\/strong><\/td><\/tr><tr><td>Styling<\/td><td><strong>Tailwind CSS v4<\/strong><\/td><\/tr><tr><td>Component layer<\/td><td><strong>daisyUI v5<\/strong><\/td><\/tr><tr><td>Small interactivity<\/td><td><strong>Alpine.js<\/strong><\/td><\/tr><tr><td>Search<\/td><td><strong>Pagefind<\/strong><\/td><\/tr><tr><td>Content<\/td><td><strong>Astro Content Collections<\/strong><\/td><\/tr><tr><td>Article format<\/td><td><strong>Markdown \/ MDX<\/strong><\/td><\/tr><tr><td>Structured data<\/td><td><strong>JSON \/ YAML<\/strong><\/td><\/tr><tr><td>Validation<\/td><td><strong>Zod<\/strong><\/td><\/tr><tr><td>Migration parser<\/td><td><strong>Cheerio<\/strong><\/td><\/tr><tr><td>HTML to Markdown<\/td><td><strong>Turndown<\/strong><\/td><\/tr><tr><td>Frontmatter generation<\/td><td><strong>gray-matter<\/strong><\/td><\/tr><tr><td>Sitemap<\/td><td><strong>@astrojs\/sitemap<\/strong><\/td><\/tr><tr><td>Rich content<\/td><td><strong>@astrojs\/mdx<\/strong><\/td><\/tr><tr><td>Images<\/td><td><strong>Astro Image \/ Picture<\/strong><\/td><\/tr><tr><td>Accessibility testing<\/td><td><strong>Playwright + axe-core<\/strong><\/td><\/tr><tr><td>SEO\/performance testing<\/td><td><strong>Lighthouse CI<\/strong><\/td><\/tr><tr><td>HTML linting<\/td><td><strong>HTMLHint<\/strong><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The clean conclusion:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">Astro + Tailwind + daisyUI + Alpine + Pagefind\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">is the best long-term stack for your large static website.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Use Bootstrap only if your old site is already deeply Bootstrap-based and speed of migration matters more than building a cleaner future design system.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Final recommended stack That is the stack I would choose over the Bootstrap variants unless your existing HTML is already heavily Bootstrap-based. Your best stack should be:&#8230; <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_joinchat":[],"footnotes":""},"categories":[11138],"tags":[],"class_list":["post-77642","post","type-post","status-publish","format-standard","hentry","category-best-tools"],"_links":{"self":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/77642","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/comments?post=77642"}],"version-history":[{"count":2,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/77642\/revisions"}],"predecessor-version":[{"id":77644,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/77642\/revisions\/77644"}],"wp:attachment":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/media?parent=77642"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/categories?post=77642"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/tags?post=77642"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}