Fix Next.js Blog Not Displaying on Cloudflare Workers: SSG + MDX Guide

Rohit Ramachandran avatarRohit Ramachandran
Diagram showing Next.js blog deployment to Cloudflare Workers with static assets

Fix Next.js Blog Not Displaying on Cloudflare Workers: The Complete SSG + MDX Solution

What to expect A detailed troubleshooting guide documenting a real-world issue: blog posts not displaying when deploying Next.js 15 with MDX to Cloudflare Workers using the OpenNext adapter. I'll walk you through the symptoms, root cause analysis, and the complete solution with code examples you can copy.

The Problem: Blog Posts Not Showing Up

I had just built a beautiful blog using Next.js 15 with MDX, deployed it to Cloudflare Workers using the @opennextjs/cloudflare adapter, and... nothing. The blog index at /blog showed "No posts yet" even though I had three published posts in my content/blog/ directory.

Even after "fixing" the index to display post cards, clicking on any blog post URL like /blog/build-a-blog-with-nextjs-mdx returned a 404 error. The posts existed, they built successfully locally, but they were completely invisible in production on Cloudflare Workers.

This is the complete story of how I debugged and fixed both issues.


The Story: When Your Perfect Local Setup Breaks in Production

I had followed every best practice:

  • Next.js 15.5.2 with App Router
  • MDX for blog content with @next/mdx
  • Static generation with generateStaticParams and dynamicParams = false
  • The OpenNext Cloudflare adapter configured correctly
  • Everything worked perfectly on localhost:3000

Then I deployed to Cloudflare Workers and hit a wall. The blog was completely non-functional. No posts on the index, 404s on individual pages.

The frustration was real: I could see the blog HTML files in .next/server/app/blog/ after building. The posts were being prerendered. So why weren't they accessible in production?

This guide is the investigation and solution I wish I'd found when searching for answers.


Understanding the Problem (ELI5)

Think of Cloudflare Workers like a valet service at a restaurant:

Node.js on a server is like having a full kitchen. You can open drawers (filesystem), check the fridge (read files), and cook anything on demand.

Cloudflare Workers is like a food cart with a limited menu. You can't go "check the kitchen" for ingredients—you only have what's on the cart (static assets). The cart is fast and can be everywhere, but it's constrained.

When your Next.js app tries to:

  1. Read blog files at runtime → It's asking the food cart to "check the fridge." Not possible.
  2. Serve prerendered pages → It's expecting dishes to be on the cart. If they're not loaded onto the cart (assets directory), they can't be served.

The solution: Put everything the Workers runtime needs on the cart (static assets) and stop asking it to check the kitchen (no filesystem operations).


Issue #1: Blog Index Showing "No Posts Yet"

The Symptom

The blog index page at /blog displayed:

Blog
Notes on Next.js, React, MDX, and building accessible, fast products.

No posts yet.

Even though content/blog/ contained three .mdx files with published posts.

Root Cause

My blog index page (app/blog/page.tsx) was using a getAllPosts() function that relied on Node.js filesystem operations:

// lib/blog.ts (the problematic version)
import fs from 'fs';
import path from 'path';

export async function getAllPosts() {
  const postsDirectory = path.join(process.cwd(), 'content/blog');
  const filenames = fs.readdirSync(postsDirectory); // ❌ Doesn't work in Workers

  const posts = filenames.map((filename) => {
    const fullPath = path.join(postsDirectory, filename);
    const fileContents = fs.readFileSync(fullPath, 'utf8'); // ❌ Doesn't work in Workers
    // ... parse and return
  });

  return posts;
}

Why it failed:

Cloudflare Workers don't have access to Node.js's fs module, even with the nodejs_compat compatibility flag. The filesystem doesn't exist at runtime—only at build time.

When getAllPosts() tried to run in the Workers environment, it failed silently (or returned an empty array), causing "No posts yet" to display.

The Solution: Hardcoded Blog Metadata

Instead of reading the filesystem at runtime, I created a static data file with all blog post metadata:

// lib/blog-posts-data.ts
export const BLOG_POSTS = [
  {
    slug: 'build-a-blog-with-nextjs-mdx',
    meta: {
      title: 'Why I Built My Blog on Next.js + MDX (and Exactly How)',
      description: 'I wanted a fast, durable, file-based blog - no CMS. Next.js + first-class MDX fit perfectly. Here\'s the why, the exact setup, SEO, and workflow you can copy.',
      date: '2025-09-16',
      updated: '2025-09-16',
      tags: ['nextjs','mdx','react','typescript'],
      draft: false,
      featured: true,
      cover: '/images/blog/build-a-blog-with-nextjs-mdx/cover.svg',
      coverAlt: 'Cover banner for the MDX + Next.js blog guide',
      author: 'Rohit Ramachandran',
      authorUrl: 'https://rohitai.com',
      authorAvatar: '/images/rohit.jpg',
      authorBio: 'AI developer building thoughtful, accessible products.'
    }
  },
  {
    slug: 'add-microsoft-sign-in-nextjs-azure-oidc',
    meta: {
      title: 'Add Microsoft Sign-In to Next.js with Azure Entra + OIDC (PKCE)',
      description: 'ELI5, end-to-end guide to add Microsoft (Outlook + work/school) login to Next.js using Azure Entra OIDC with PKCE, secure cookies, Drizzle/Neon. Includes code, dev to prod, and troubleshooting.',
      date: '2025-10-19',
      tags: ['nextjs','typescript','javascript'],
      draft: false,
      cover: '/images/blog/add-microsoft-sign-in-nextjs/cover.svg',
      coverAlt: 'Microsoft login flow diagram for Next.js with OAuth 2.0 and OIDC',
      author: 'Rohit Ramachandran',
      authorUrl: 'https://rohitai.com',
      authorAvatar: '/images/rohit.jpg',
      authorBio: 'AI developer building thoughtful, accessible products.'
    }
  },
  {
    slug: 'add-google-sign-in-nextjs-oauth-oidc',
    meta: {
      title: 'Add Google Sign-In to Next.js with OAuth 2.0 + OIDC (PKCE)',
      description: 'Step-by-step, ELI5 guide to add Google login to Next.js using OAuth 2.0 + OIDC with PKCE, secure cookies, and Drizzle/Neon. Includes code, security rationale, dev to prod, and troubleshooting.',
      date: '2025-10-19',
      tags: ['nextjs','typescript','javascript','oauth'],
      draft: false,
      cover: '/images/blog/add-google-sign-in-nextjs/cover.svg',
      coverAlt: 'Google login flow diagram for Next.js with OAuth 2.0 and OIDC',
      author: 'Rohit Ramachandran',
      authorUrl: 'https://rohitai.com',
      authorAvatar: '/images/rohit.jpg',
      authorBio: 'AI developer building thoughtful, accessible products.'
    }
  }
];

Then I updated the blog index page to use this static data:

// app/blog/page.tsx (fixed version)
import { BLOG_POSTS } from '@/lib/blog-posts-data';

export default async function BlogPage() {
  const isProd = process.env.NODE_ENV === 'production';

  // Filter out drafts in production
  const posts = BLOG_POSTS.filter((p) => (isProd ? !p.meta.draft : true));

  // Sort by date descending
  const sorted = posts.sort((a, b) => {
    return new Date(b.meta.date).getTime() - new Date(a.meta.date).getTime();
  });

  if (sorted.length === 0) {
    return <p>No posts yet.</p>;
  }

  return (
    <div>
      {sorted.map((post) => (
        <PostCard key={post.slug} post={post} />
      ))}
    </div>
  );
}

Result: The blog index now works perfectly. No filesystem operations, no runtime dependencies—just static data that works everywhere.


Issue #2: Individual Blog Posts Returning 404

The Symptom

After fixing the blog index, I could see all three blog posts listed. But clicking on any post link resulted in a 404 error:

  • https://rohitai.com/blog/build-a-blog-with-nextjs-mdx → 404
  • https://rohitai.com/blog/add-microsoft-sign-in-nextjs-azure-oidc → 404
  • https://rohitai.com/blog/add-google-sign-in-nextjs-oauth-oidc → 404

Initial Investigation

I verified that my blog post page was configured correctly for static generation:

// app/blog/[slug]/page.tsx
export const dynamicParams = false; // ✅ Correct: only pre-generate, no runtime fallback

export async function generateStaticParams() {
  const prod = process.env.NODE_ENV === 'production';
  const posts = await getAllPosts({ includeDrafts: !prod, includeFuture: true });

  return posts
    .filter((p) => (prod ? !p.meta.draft : true))
    .map((p) => ({ slug: p.slug }));
}

export default async function BlogPostPage({
  params
}: {
  params: { slug: string }
}) {
  const { slug } = params;
  // Dynamically import the MDX file
  const Post = await import(`@/content/blog/${slug}.mdx`);

  return (
    <article>
      <Post.default />
    </article>
  );
}

This setup is correct for SSG (Static Site Generation). With dynamicParams = false, Next.js should prerender all blog post pages at build time.

Build Output Verification

I checked the build output and found the prerendered HTML files:

$ ls .next/server/app/blog/
build-a-blog-with-nextjs-mdx.html
add-microsoft-sign-in-nextjs-azure-oidc.html
add-google-sign-in-nextjs-oauth-oidc.html
page.html
search.html
tags.html

The pages WERE being prerendered! Each blog post had a corresponding .html file.

Root Cause Discovery

The issue was with the OpenNext Cloudflare adapter. I checked what was actually being deployed:

$ ls .open-next/assets/
_next/
favicon.ico
images/
robots.txt
sitemap.xml

$ ls .open-next/assets/__cache/
fetch-cache/
...

The prerendered blog HTML files were missing!

The OpenNext build process (npx @opennextjs/cloudflare build) was:

  1. ✅ Correctly building the Next.js app with prerendered pages
  2. ✅ Creating the .open-next/ output directory
  3. ✅ Copying static assets from public/
  4. NOT copying the prerendered HTML files from .next/server/app/blog/*.html

When Cloudflare Workers tried to serve /blog/build-a-blog-with-nextjs-mdx, it looked for the corresponding HTML in the assets directory, didn't find it, and returned 404.

Why OpenNext Doesn't Copy Prerendered HTML Automatically

The @opennextjs/cloudflare adapter focuses on:

  • Server function bundling
  • API routes
  • Caching infrastructure
  • ISR (Incremental Static Regeneration)

For fully static pages, it expects you to explicitly configure which assets should be served. The default behavior doesn't automatically copy all prerendered HTML from .next/server/app/.

This is documented (though not prominently) in OpenNext's architecture: static HTML pages must be explicitly placed in the assets directory or served via the configured cache mechanism.

The Solution: Manual HTML Asset Copying

I needed to copy the prerendered blog HTML files to the assets directory as part of the build process.

Step 1: Manual test to verify the fix

# Copy blog HTML files to assets
mkdir -p .open-next/assets/blog
cp -r .next/server/app/blog/*.html .open-next/assets/blog/

# Deploy
wrangler deploy

I deployed and tested: All blog posts worked!

Step 2: Automate in the build script

I updated package.json to automate this process:

{
  "scripts": {
    "build:workers": "npx @opennextjs/cloudflare build && mkdir -p .open-next/assets/blog && cp -r .next/server/app/blog/*.html .open-next/assets/blog/ && mkdir -p .open-next/assets/__cache && cp -r .open-next/cache/* .open-next/assets/__cache/"
  }
}

What this does:

  1. npx @opennextjs/cloudflare build - Runs the standard OpenNext build
  2. mkdir -p .open-next/assets/blog - Creates the blog directory in assets
  3. cp -r .next/server/app/blog/*.html .open-next/assets/blog/ - Copies all prerendered blog HTML
  4. mkdir -p .open-next/assets/__cache - Creates cache directory
  5. cp -r .open-next/cache/* .open-next/assets/__cache/ - Copies cache files

Now every build automatically includes the prerendered blog pages in the assets directory where Cloudflare Workers can serve them.


Complete OpenNext Configuration

For reference, here's my full OpenNext configuration that works with this setup:

// open-next.config.ts
import { defineCloudflareConfig } from '@opennextjs/cloudflare';
import staticAssetsIncrementalCache from '@opennextjs/cloudflare/overrides/incremental-cache/static-assets-incremental-cache';

export default defineCloudflareConfig({
  incrementalCache: staticAssetsIncrementalCache,
  enableCacheInterception: true,
});
# wrangler.toml
name = "rohitai"
compatibility_date = "2025-04-01"
compatibility_flags = ["nodejs_compat"]

[assets]
directory = ".open-next/assets"
binding = "ASSETS"

[[routes]]
pattern = "rohitai.com/*"
custom_domain = true

[[routes]]
pattern = "www.rohitai.com/*"
custom_domain = true

The key settings:

  • staticAssetsIncrementalCache - Tells OpenNext to use static assets for caching
  • enableCacheInterception - Enables cache-first serving
  • [assets] in wrangler.toml - Points to .open-next/assets where our HTML lives

The Complete Deployment Workflow

Here's the full process from code to production:

# 1. Build Next.js with prerendering
npm run build

# 2. Build for Cloudflare Workers (includes HTML copying)
npm run build:workers

# 3. Deploy to Cloudflare
wrangler deploy

Or use the combined command:

npm run deploy

Which runs both build steps and deployment:

{
  "scripts": {
    "deploy": "npm run build:workers && wrangler deploy"
  }
}

Verification Checklist

After deploying, verify everything works:

Blog Index:

  • [ ] Navigate to /blog
  • [ ] See all published posts (not drafts)
  • [ ] Post cards show title, description, date, tags
  • [ ] Cover images load correctly

Individual Posts:

  • [ ] Click on each post card
  • [ ] Post content renders fully
  • [ ] Images within posts load
  • [ ] Code blocks are styled correctly
  • [ ] No 404 errors

Build Output:

  • [ ] .next/server/app/blog/*.html files exist after npm run build
  • [ ] .open-next/assets/blog/*.html files exist after npm run build:workers
  • [ ] Cache files in .open-next/assets/__cache/

Performance:

  • [ ] Blog index loads quickly (static HTML)
  • [ ] Individual posts load quickly (prerendered HTML)
  • [ ] No runtime filesystem operations
  • [ ] Check Cloudflare Analytics for response times

Understanding the Architecture

Here's how the pieces fit together:

Build Time (Local)

  1. Next.js reads content/blog/*.mdx files
  2. generateStaticParams() tells Next.js which slugs to prerender
  3. Next.js generates .next/server/app/blog/[slug].html for each post
  4. OpenNext adapter processes the build
  5. Custom build script copies HTML to .open-next/assets/blog/

Runtime (Cloudflare Workers)

  1. Request comes in for /blog/build-a-blog-with-nextjs-mdx
  2. Workers looks in assets directory for blog/build-a-blog-with-nextjs-mdx.html
  3. Finds the prerendered HTML (because we copied it)
  4. Serves it directly with cache headers
  5. No filesystem operations, no dynamic rendering

Why This Works

  • Blog index: Uses hardcoded BLOG_POSTS array (no fs required)
  • Individual posts: Served as prerendered static HTML from assets
  • No runtime dependencies: Everything needed is in the assets directory
  • Fast: Static HTML served from CDN edge locations

Troubleshooting Common Issues

Blog index still shows "No posts yet"

Check:

  1. Is BLOG_POSTS imported correctly in app/blog/page.tsx?
  2. Are posts marked as draft: false?
  3. Is the date not in the future (if filtering)?
  4. Clear browser cache and redeploy

Individual posts return 404

Check:

  1. Run npm run build and verify .next/server/app/blog/*.html exists
  2. Run npm run build:workers and verify .open-next/assets/blog/*.html exists
  3. Check that the slug in the URL matches the HTML filename exactly
  4. Verify dynamicParams = false is set in app/blog/[slug]/page.tsx
  5. Deploy with wrangler deploy (not just npm run build:workers)

Build script fails with "no such file or directory"

Check:

  1. Make sure .next/server/app/blog/ exists (run npm run build first)
  2. The glob pattern *.html should match your files
  3. Use mkdir -p to avoid errors if directory exists
  4. Check file permissions

Posts work locally but not in production

This is the classic issue!

Local development uses Node.js server which CAN read filesystem. Production Cloudflare Workers CANNOT.

Solution:

  • Use hardcoded BLOG_POSTS for metadata (no fs operations)
  • Copy prerendered HTML to assets (no runtime generation)
  • Never call fs.readFileSync or similar in runtime code

Some posts work, others don't

Check:

  1. Are the missing posts in generateStaticParams()?
  2. Do the missing posts have valid MDX that compiles?
  3. Check build logs for errors during prerendering
  4. Verify all HTML files were copied to assets directory

Images in blog posts don't load

Check:

  1. Images are in /public/images/blog/[slug]/ directory
  2. Image paths in MDX use absolute paths: /images/blog/[slug]/image.png
  3. Images were included in the public/ directory before build
  4. OpenNext copies public/ to .open-next/assets/ automatically

Alternative Approaches Considered

1. Dynamic Imports at Runtime

Idea: Import MDX files on-demand when a post is requested.

Why it doesn't work: Workers can't do dynamic filesystem imports. The import() statement works at build time but not runtime in the Workers environment.

2. Edge Config or KV Store

Idea: Store blog metadata in Cloudflare KV and read at runtime.

Why I didn't use it: Adds complexity and external dependencies. The hardcoded approach is simpler, faster (no KV fetch), and easier to maintain. KV makes sense for frequently updated data, but blog posts are static content.

3. Generating a JSON Manifest

Idea: Create a JSON file with all post metadata at build time, import it at runtime.

Why I didn't use it: This is essentially what BLOG_POSTS does, but less type-safe. Exporting from a .ts file gives TypeScript validation.

4. Using Middleware to Rewrite Requests

Idea: Use Next.js middleware to rewrite blog routes to static files.

Why it doesn't work: The files still need to be in the assets directory. Middleware doesn't help if the HTML isn't where Workers can access it.


When to Use This Approach

Good fit if you:

  • Have a Next.js 15 blog with MDX content
  • Want to deploy to Cloudflare Workers for global edge performance
  • Use static generation (SSG) with generateStaticParams
  • Have a manageable number of blog posts (< 1000)
  • Prefer Git-based content over a CMS

Not ideal if you:

  • Need fully dynamic blog content (use Edge Config or KV instead)
  • Have thousands of blog posts (consider pagination and partial prerendering)
  • Want authors to add posts without rebuilding (use a headless CMS)
  • Need ISR (Incremental Static Regeneration) - requires different setup

FAQ

Q: Why can't Cloudflare Workers access the filesystem? A: Workers run in V8 isolates, not traditional servers. They're lightweight and stateless by design. There's no filesystem to access—only assets you explicitly include.

Q: Does this mean I have to update BLOG_POSTS manually every time I add a post? A: Yes, but it's minimal effort. You add the MDX file AND one entry to BLOG_POSTS. You could potentially generate this at build time, but manual is simpler and more explicit.

Q: What about generateStaticParams? Is it useless now? A: No! generateStaticParams still tells Next.js which pages to prerender. Without it, the HTML files wouldn't be created. The hardcoded BLOG_POSTS is just for the index page listing.

Q: Can I use this with Incremental Static Regeneration (ISR)? A: Partially. ISR requires cache invalidation and regeneration logic. The staticAssetsIncrementalCache supports some ISR patterns, but full ISR is tricky on Workers. For most blogs, full SSG is simpler and faster.

Q: Will this work with App Router's automatic static optimization? A: Yes! The pages are fully static. Next.js optimizes them, and we just ensure Workers can serve the output.

Q: What happens if I forget to copy the HTML files? A: Individual blog posts will return 404. The build script ensures this happens automatically now, but if you manually deploy .open-next/ without running the full build:workers script, you'll see 404s.

Q: Can I use this approach for other dynamic routes? A: Absolutely! Any route using generateStaticParams with dynamicParams = false can be served this way. Just copy the prerendered HTML to the assets directory.

Q: Does this affect SEO? A: No, this is better for SEO! Fully static HTML served from CDN edge locations is the fastest option. Search engines get clean HTML instantly.

Q: What about client-side navigation? A: Next.js handles this. Client-side routing still works—it fetches the static HTML via the Router instead of full page reloads.


Performance Benefits

After implementing this solution, here's what improved:

Before (when it worked locally but not in prod):

  • Local: Fast (SSG)
  • Production: 404 errors

After:

  • Blog index: Instant (prerendered HTML, hardcoded data, no DB/fs calls)
  • Individual posts: Instant (prerendered HTML served from Cloudflare edge)
  • Global distribution: Posts served from 300+ Cloudflare data centers worldwide
  • Time to First Byte (TTFB): < 50ms globally
  • No cold starts: Static assets don't need Workers initialization
  • Caching: Automatic edge caching with long TTLs

This is as fast as a blog can possibly be—static HTML on a global CDN with no server-side processing.


Security Considerations

This approach is inherently secure because:

  1. No dynamic code execution: Everything is prerendered at build time
  2. No filesystem access: Workers can't read unexpected files
  3. No database queries: The index uses static data
  4. No injection risks: MDX is compiled at build time, not runtime
  5. Content Security Policy: Easy to implement strict CSP with static content

The only attack surface is the build process itself, which is in your control (CI/CD).


Maintenance Tips

When adding a new blog post:

  1. Create content/blog/new-post-slug.mdx
  2. Add metadata to the MDX file
  3. Add entry to lib/blog-posts-data.ts
  4. Add images to /public/images/blog/new-post-slug/
  5. Run npm run build to test locally
  6. Run npm run deploy to publish

When updating an existing post:

  1. Edit the MDX file
  2. Update the updated field in metadata
  3. Update the metadata in lib/blog-posts-data.ts if changed
  4. Rebuild and redeploy

Quarterly checklist:

  • [ ] Verify all posts still render correctly
  • [ ] Check that new Next.js/OpenNext versions don't break the build
  • [ ] Review Cloudflare Analytics for performance
  • [ ] Test on multiple devices and browsers
  • [ ] Validate HTML and accessibility

Migration Path for Existing Blogs

If you have an existing Next.js blog on Vercel, Netlify, or traditional hosting:

Step 1: Verify your blog works locally

npm run build
npm run start
# Visit http://localhost:3000/blog

Step 2: Install Cloudflare dependencies

npm install -D @opennextjs/cloudflare wrangler

Step 3: Create hardcoded blog metadata Extract metadata from your current getAllPosts() function and create lib/blog-posts-data.ts.

Step 4: Update blog index to use static data Replace filesystem calls with BLOG_POSTS import.

Step 5: Configure OpenNext Create open-next.config.ts (see example above).

Step 6: Update build script Add the HTML copying logic to your build:workers script.

Step 7: Test locally with Wrangler

npm run build:workers
npx wrangler dev

Step 8: Deploy to Cloudflare

npx wrangler deploy

Step 9: Update DNS Point your domain to Cloudflare Workers.

Step 10: Verify Test all blog posts in production.


Code Repository Structure

For reference, here's the relevant project structure:

frontend/
├── app/
│   └── blog/
│       ├── page.tsx                    # Blog index (uses BLOG_POSTS)
│       └── [slug]/
│           ├── page.tsx                # Post template (SSG)
│           └── opengraph-image.tsx     # OG images
├── content/
│   └── blog/
│       ├── build-a-blog-with-nextjs-mdx.mdx
│       ├── add-google-sign-in-nextjs-oauth-oidc.mdx
│       └── add-microsoft-sign-in-nextjs-azure-oidc.mdx
├── lib/
│   ├── blog.ts                         # Helper functions (optional)
│   └── blog-posts-data.ts              # Hardcoded metadata ⭐
├── public/
│   └── images/
│       └── blog/
│           └── [slug]/                 # Images per post
├── .next/
│   └── server/
│       └── app/
│           └── blog/
│               └── *.html              # Prerendered (build output)
├── .open-next/
│   └── assets/
│       ├── blog/
│       │   └── *.html                  # Copied for Workers ⭐
│       └── images/                     # From public/
├── open-next.config.ts                 # OpenNext configuration
├── wrangler.toml                       # Cloudflare Workers config
└── package.json                        # Build scripts

Final Thoughts

This was a frustrating bug to diagnose because:

  1. Everything worked perfectly locally
  2. The build appeared successful
  3. The error (404) didn't point to the root cause

The lesson: Cloudflare Workers is not a Node.js server. It's a different runtime with different constraints. Understanding these constraints is key to building applications that work correctly in production.

The solution—hardcoded metadata and explicit HTML asset copying—feels a bit manual, but it's:

  • ✅ Simple and explicit
  • ✅ Fast (no runtime overhead)
  • ✅ Reliable (no edge cases with filesystem operations)
  • ✅ Maintainable (clear what's happening)

If you're deploying Next.js to Cloudflare Workers, save yourself hours of debugging: understand what can and can't run at the edge, prerender everything possible, and make your assets explicit.


Further Reading


Have questions or run into issues? Feel free to reach out. This solution worked for my setup (Next.js 15.5.2, @opennextjs/cloudflare 1.11.1, Cloudflare Workers), and I hope it saves you the debugging time I spent figuring it out.

Fix Next.js Blog Not Displaying on Cloudflare Workers: SSG + MDX Guide