Don't Let Google Miss Your Articles
SSR Blog's SEO Pitfalls: A Firsthand Account
The reasons behind every SEO decision when building a blog with Astro + PostgreSQL—and the pitfalls that almost made the articles disappear from search results.
Why Is SEO for the SSR Blog Different from That of a Static Site?
Dynamic content is not included in the sitemap
SSR pages are generated at runtime; @astrojs/sitemap only includes static pages generated at build time.
The article was published but isn't in the sitemap, so Google has no idea it exists.
What have we done?
We added five layers, ranging from the dynamic sitemap to the robots.txt file, with each layer corresponding to a specific user.
sitemap-posts.xml retrieves published articles from the database in real time and automatically patches them into the build's sitemap-index.xmlhttp://localhost and https://blog.joneshong.com being flagged as duplicates/admin/ and /api/. Points to two sitemapsCore Web Vitals Optimization
Astro's Secret Weapon: By default, no JavaScript is included. React components are only hydrated where interaction is required (client:only), and the rest is all static HTML.
How did we discover the problem?
It wasn't planned in advance; I had to fix three issues before everything was complete: the sitemap wasn't being linked to, the estimated reading time was calculated as 73 minutes, and the Nginx subpath pages were blank.
sitemap-posts.xml: no one has cited it.
@astrojs/sitemap generates sitemap-index.xml. At this time, it only includes static pages. The dynamic sitemap-posts.xml was ignored. Code fix: After "build," automatically patch "sitemap-index" to include the posts sitemap.
Insight: Automation tools don't necessarily cover all cases; dynamic SSR pages require manual troubleshooting.
Rich HTML's content.length includes the entire CSS
content.length / 400 calculates the reading time, but the Rich HTML content has 25,000+ characters of CSS + HTML tags. I need to strip the tags first so the calculated reading time is accurate.
Insight: For any logic related to the length of the content, you must first determine the format of the content.
Vite dev server + subpath = Dead end
Vite uses absolute paths for ES module imports; nginx's sub_filter — there's no end to the changes. Solution: In dev preview, don't use the Vite dev server; use the built-in SSR server instead.
Insight: Astro Islands' component-url property also needs to be rewritten by `sub_filter`.
Paste your own `head` here to check
There are six items on the list above, and three of them can be verified without crawling the site—just paste the URL of any one of your articles <head> — the source code is sufficient. Canonical, hreflang, and JSON-LD are all parsed locally in the browser and do not send anything out. The remaining three (sitemap inclusion, robots.txt, and TTFB) require actual requests to be made to your domain, which browser cross-site limitations cannot do. Leave those three for the AI assigned in the prompt above to crawl.
It's checking "the string of text you pasted", not your online page—if what you pasted is the Elements panel from browser DevTools (which is the DOM after JS has run), the result might be different from what a web crawler sees. To match what the crawler sees, please use the "View Page Source" (view-source) version.
Copy this to your AI Agent
You might think that once an article is published, Google doesn't know it exists—SSR blog pages are generated at runtime, and @astrojs/sitemap only collects static pages at build time, so dynamic posts aren't written to the sitemap at all. Missing meta tags at worst leads to poor rankings, but not being indexed means disappearing from search results entirely.
Copy the text below and paste it into your AI. Tell it not to rely on common sense—instead, have it crawl your site, compare it to the list in this article, and identify exactly which items you’ve actually omitted:
Further Reading
Each of these five articles corresponds to a specific decision, covering topics ranging from the hidden-cost analogy to data on a 67% hreflang error rate.
| Article | Why It Matters |
|---|---|
| Apideck: Your MCP Server Is Consuming Your Context Window | Inspired the analogy of “hidden costs” |
| SEO for Astro: How to Make the Fastest Framework Also the Smartest | A Complete Guide to Implementing Astro SEO |
| JSON-LD Blog Post Example | Reference Implementation of the BlogPosting Schema |
| Multilingual SEO and Hreflang Guide | Source of the 67% error rate data |
| How to Improve Core Web Vitals | Specific Methods for Optimizing SSR Performance |
SEO is an engineering discipline, not a form of mysticism.
Every decision has a reason, and every setback offers a lesson.
SEO for the SSR Blog isn't something that ends once the meta tags are set up; it's an ongoing, iterative process.
Question 1: Dynamic articles are not included in the sitemap
→ Manual correction sitemap-posts.xml
Question 2: SSR without caching
→ Static Page prerender + Dynamic Pages SSR
Question 3: Multilingual SEO is prone to errors
→ hreflang Two-way + x-default
Verification: Google Search Console Manual Submission + Monitoring
// Induction
Astro SSR + Automated SEO Pipeline
No omissions, no duplicates, no delays