Problem description
On zhimalab.tech, the Math Knowledge Map page rendered incorrectly in production:
- The page showed only the knowledge map itself (knowledge cards on a dark background) — missing the site’s navigation bar, breadcrumb and footer
- With
pnpm run devlocally everything was fine; the page had the full navigation and layout
Investigation
1. Page structure analysis
The page has two layers:
- The Astro page
src/pages/tutorial/math.astro— uses theBaseLayoutwith navigation bar and breadcrumb, embedding the static content via an iframe - Static HTML files in the
public/tutorial/math/directory — containingindex.html(the knowledge map home page) plus 95+ knowledge-point pages
The Astro page code roughly looks like:
---
import BaseLayout from "../../layouts/BaseLayout.astro";
---
<BaseLayout title="小学数学" ... layoutType="tool-detail">
<div class="w-full h-[calc(100vh-3.5rem)]">
<iframe src="/tutorial/math/index.html" ...></iframe>
</div>
</BaseLayout>
2. Build output comparison
After astro build, the dist/ directory structure looks like:
dist/
tutorial/
math.html ← Astro page build output
math/
index.html ← static file from public/
1上-数一数比多少.html
...
Astro builds src/pages/tutorial/math.astro into dist/tutorial/math.html, and also copies public/tutorial/math/index.html to dist/tutorial/math/index.html.
3. Behavioral difference in production
In local dev (pnpm run dev), the Astro dev server handles routing correctly:
- Visiting
/tutorial/math→ renders themath.astropage - Visiting
/tutorial/math/index.html→ serves the static file
But in the Cloudflare Pages production environment, when you visit /tutorial/math/, the server sees the directory dist/tutorial/math/, and since that directory has an index.html, it directly returns the static file, completely bypassing the Astro page. That’s why only the knowledge-map content showed, without the site’s navigation layout.
The solution
Change the Astro page’s route path to one that doesn’t conflict with the static directory:
Steps
- Rename
src/pages/tutorial/math.astrotosrc/pages/tutorial/math-map.astro - Update all links pointing to
/tutorial/mathto/tutorial/math-map - Leave the iframe’s
src="/tutorial/math/index.html"unchanged (it still points to the static file)
After the change:
src/pages/tutorial/
math.astro → math-map.astro (renamed)
index.astro ← link changed to /tutorial/math-map
src/pages/
index.astro ← link changed to /tutorial/math-map
Why does this fix it?
After renaming, the build output becomes:
dist/
tutorial/
math-map/
index.html ← Astro page (navigation layout + iframe)
math/
index.html ← static knowledge map content
Now visiting /tutorial/math-map/ serves the Astro page, and visiting /tutorial/math/ serves the static knowledge map. The two no longer conflict.
Takeaways
Astro’s public/ directory vs routes
This is a common pitfall in Astro projects. Files in public/ are copied directly to the same path in dist/ after building, and Astro’s page routes also generate HTML files in dist/. When the two paths overlap, static servers (like Cloudflare Pages, Nginx) prefer the index.html in the directory over a same-named single file.
Preventive measures
- Avoid creating directories in
public/with the same name assrc/pages/routes - If you need to store many static files in
public/, consider putting them under a distinctive subpath (e.g.public/static/) - Or keep a clear distinction between file names in
public/and page route names
Dev vs production differences
Problems like this are hard to spot in local development because the Astro dev server has smarter routing logic. Always preview in production mode with astro build && astro preview to catch these issues early.