Last updated: September 2026 · Reading time: 15 min · By the Best Web Developer team
Quick answer
To build a blog with Next.js 15 and MDX, create an App Router project, install @next/mdx, add an mdx-components file, store posts as MDX files that export metadata, and render them from a dynamic route that awaits its params and pre-builds every slug with generateStaticParams. Add metadata, a sitemap, JSON-LD and syntax highlighting, then deploy as static pages.
Key takeaways
- MDX lets you write posts in Markdown and drop React components such as charts, callouts or sign-up forms straight into the text.
- In Next.js 15, params and searchParams in pages are Promises, so dynamic routes must await them.
- The App Router with @next/mdx requires an mdx-components.tsx file at the project root, or the build fails.
- Static generation gives most blogs excellent Core Web Vitals and very low hosting costs.
- For Saudi audiences, plan Arabic RTL routes, hreflang and bilingual metadata from the first commit.
Why build a blog with Next.js 15 and MDX
A blog with Next.js gives you the speed of static HTML, the flexibility of React and full control of your SEO, without a database or a plugin stack to patch every month. Pairing it with MDX keeps writing simple while letting developers add interactive pieces where they help.
MDX is a file format that combines Markdown with JSX, so you can write headings and paragraphs as normal text and import React components inside the same file. Next.js is a React framework by Vercel that handles routing, rendering and optimisation; version 15 uses React 19 and the App Router as the recommended default.
This tutorial builds a working blog step by step: project setup, MDX configuration, a posts folder, an index page, dynamic post pages, SEO metadata, a sitemap and deployment. The code follows the official Next.js documentation patterns and uses TypeScript.
Note: versions used
Examples assume Next.js 15 with the App Router, React 19, TypeScript and Node.js 18.18 or later. Package APIs change over time, so if a command fails, check the current Next.js MDX guide. Code samples are shortened for readability; add your own error handling and styling.
Step by step: set up a blog with Next.js and MDX
Setup takes about 15 minutes: create the project, install four MDX packages, update the Next.js config and add one components file. After that, every MDX file you add becomes a page.
Step 1: create the project
Run the official starter and accept TypeScript, ESLint, the App Router and the src directory if you like that layout. Tailwind CSS is optional but useful for typography.
Step 2: configure MDX in next.config.mjs
Wrap your config with createMDX and add md and mdx to the page extensions. Remark and rehype plugins go in the options object; when using Turbopack, pass plugin names as strings rather than imported functions.
Step 3: add mdx-components.tsx
The App Router needs this file at the root of the project (or inside src). It is also where you map Markdown elements to your own components, for example to use next/image for every image.
Step 4: write your first post
Create content/posts/hello-world.mdx. Instead of YAML frontmatter, which @next/mdx does not parse by default, export a metadata object at the top of the file. Your editor will type-check it and you avoid an extra plugin.
Routing, listing and rendering posts
A blog with Next.js needs two routes: an index at /blog that lists posts, and a dynamic route at /blog/[slug] that renders one. A small helper reads the posts folder so both routes share the same data.
Step 5: a helper to load posts
Create lib/posts.ts. It lists MDX files, imports each file’s metadata and sorts posts newest first. Because it runs on the server at build time, using the Node file system is safe.
Step 6: the dynamic post page
This is where Next.js 15 differs from older tutorials: params is a Promise, so you await it. Setting dynamicParams to false returns a 404 for any slug that was not generated at build time.
Step 7: the blog index
The index page is a server component that calls getPosts and renders a list of links with titles, dates and descriptions. Add pagination once you pass about 30 posts, and consider tag pages generated the same way, one static page per tag.
Pro tip
Slugs become permanent URLs, so keep them short, lowercase and hyphenated, and never change them after publishing without a redirect. Our free slug generator turns any title, including accented text, into a clean URL-safe filename for your posts folder.
SEO, structured data and performance
Next.js has built-in APIs for metadata, sitemaps and robots files, so a well-built blog with Next.js can match or beat a tuned WordPress site on technical SEO. Add them before launch rather than after.
Step 8: per-post metadata
Export generateMetadata from the post route. It receives the same Promise-based params, loads the post’s metadata and returns the title, description, canonical URL and Open Graph data.
Write titles under about 60 characters and descriptions of 130–160. Our meta tag generator previews how they will look in search results before you commit them to the MDX file.
Step 9: sitemap, robots and JSON-LD
Create app/sitemap.ts that returns one entry per post from getPosts, and app/robots.ts that points to it. In the post page, render a script element of type application/ld+json containing a BlogPosting object with headline, datePublished, author and image, following the vocabulary at schema.org. Add an RSS feed with a route handler at app/feed.xml/route.ts so readers and aggregators can subscribe.
Step 10: highlighting and performance
Use rehype-pretty-code (built on Shiki) to highlight code at build time, so no highlighting JavaScript ships to the browser. Add remark-gfm for tables and task lists. Use next/image for post images with explicit width and height, and next/font to self-host fonts. A static MDX blog built this way commonly scores LCP well under 2.5 seconds and CLS near zero on mobile.
Highlight: our pre-launch checklist
1) Every post has a unique title, description and canonical URL. 2) The sitemap lists every post and returns 200. 3) robots.ts allows crawling in production and blocks preview deployments. 4) BlogPosting JSON-LD validates. 5) Images use next/image with alt text. 6) A 404 page exists and unknown slugs return 404. 7) Lighthouse mobile scores 90+ on a real post, not only the homepage.
Key benefits of an MDX blog
A blog with Next.js and MDX is fast, cheap to host, secure by default and easy to version. The trade-off is that non-technical writers need a simple workflow or a Git-based CMS on top.
- Speed: pre-rendered HTML from a CDN loads quickly on mobile networks.
- Security: no database or admin login to attack on a static build.
- Low running cost: many static blogs run on free or low-cost hosting tiers.
- Version control: every post change is a Git commit with history and review.
- Interactive content: calculators, charts and demos sit inside posts as React components.
- Full SEO control: metadata, schema and URLs are code, not plugin settings.
“The best blog stack is the one your writers will actually use. MDX is perfect for technical teams; give everyone else a friendly editing layer on top.”
— Best Web Developer strategy team
Use cases: bilingual MDX blogs in Saudi Arabia
Saudi tech companies, SaaS startups and professional firms benefit most from a bilingual blog with Next.js and MDX, because they publish in Arabic and English and want fast, controllable SEO. The main extra work is RTL layout and language routing.
Arabic RTL and language routes
Put the language in the URL, for example /ar/blog and /en/blog, using an app/[lang] segment. In the root layout, set the html lang attribute from the segment and set dir to rtl for Arabic. Use CSS logical properties such as padding-inline-start so layouts mirror automatically, as described in the MDN Web Docs. Store Arabic posts in content/posts/ar and English in content/posts/en.
Hreflang, hosting and data
Return alternates.languages from generateMetadata so each post links its ar-SA and en versions, which helps Google show the right language. A static blog stores no personal data, but newsletter forms and analytics do, so check PDPL requirements and consent before adding them. If you add server features, choose a hosting region close to the Gulf for lower latency.
Illustrative case: a Riyadh SaaS startup moves its blog to MDX
Challenge: A B2B software company ran a bilingual WordPress blog with 22 plugins, a 3.9 s mobile LCP and frequent broken Arabic layouts after updates. The engineering team wanted posts in the same repository as the product site.
What we did: We migrated 140 posts to MDX with ar and en routes, built the listing, tag pages, sitemap, RSS and BlogPosting schema, and set up rehype-pretty-code for code samples. Budget: SAR 28,000 over four weeks.
Result: Mobile LCP fell from 3.9 s to 1.4 s and CLS from 0.21 to 0.02. Hosting cost for the blog dropped to near zero, and organic sessions to the blog grew about 35% over the following five months with no change in publishing frequency.
@next/mdx vs other content approaches
For a blog with Next.js, @next/mdx is the simplest choice for posts stored in your repository. If content lives in a CMS or you need schema validation across hundreds of posts, another approach may fit better.
| Approach | Where content lives | Best for | Trade-offs |
|---|---|---|---|
| @next/mdx | MDX files in the repo | Developer blogs, docs, small teams | No frontmatter by default, writers need Git |
| Remote MDX compilation | Files, database or CMS | Content fetched at build or request time | More setup, must sanitise untrusted input |
| Typed content layer (for example Velite) | Files with validated schemas | Large blogs needing type-safe metadata | Extra build step and dependency |
| Headless CMS | Hosted CMS | Marketing teams, many writers | Monthly fees, API integration work |
| WordPress | Database | Non-technical editors, plugin ecosystem | Updates, security patching, slower by default |
Typical build budgets for a production blog with Next.js in the Gulf range from SAR 8,000–20,000 for a single-language MDX blog to SAR 25,000–60,000 for a bilingual blog with CMS integration and migration. Our web development service covers both, including content migration from WordPress.
Highlight: deployment options
Vercel deploys Next.js with zero configuration. Cloudflare and other platforms support it through adapters. For a fully static blog, set output to export in next.config and upload the generated out folder to any CDN or object storage. Self-hosting with next start in a Node container works when you need server features in a specific region.
What is next: React Server Components, AI search and Vision 2030
The direction of Next.js is more server rendering, less client JavaScript and faster builds. At the same time, AI search and Vision 2030 content demand are changing what a technical blog must deliver.
- Server Components by default: MDX posts render on the server, so interactive components should be small client islands.
- Turbopack maturity: faster local development and builds make large MDX collections practical.
- AI answers: clear headings, direct answers and structured data help AI assistants cite your posts; see our guide to technical SEO site health.
- Arabic technical content: Vision 2030 digital skills programmes are growing the audience for Arabic developer tutorials, still an underserved area.
- Content as code: teams increasingly review posts in pull requests, with automated checks for links, images and metadata.
Frequently Asked Questions about Next.js MDX blogs
How do I add MDX to a Next.js 15 App Router project?
Install @next/mdx, @mdx-js/loader, @mdx-js/react and @types/mdx. Wrap your next.config file with createMDX and add md and mdx to pageExtensions. Then create an mdx-components.tsx file at the project root that exports a useMDXComponents function. After that, MDX files can be used as pages or imported into routes like any other component.
Why is params a Promise in Next.js 15?
Next.js 15 made request-specific APIs such as params, searchParams, cookies and headers asynchronous so the framework can start rendering before all request data is available. In a dynamic route you must await params before reading the slug. Older tutorials that read params.slug directly will show warnings or errors, so update those patterns when you upgrade.
Does @next/mdx support frontmatter?
Not by default. The simplest approach is to export a metadata object from each MDX file, which your routes can import alongside the default component. If you prefer YAML frontmatter, add remark plugins such as remark-frontmatter and remark-mdx-frontmatter, or parse files yourself with a library like gray-matter and compile them with a remote MDX approach.
Is a Next.js MDX blog good for SEO?
Yes, when set up properly. Static pages load fast, and Next.js provides built-in APIs for titles, descriptions, canonical URLs, Open Graph data, sitemaps and robots files. Add BlogPosting structured data, descriptive slugs and internal links. The main risk is forgetting these pieces, since unlike WordPress there is no SEO plugin filling gaps automatically.
How do I build an Arabic and English blog in Saudi Arabia with Next.js?
Use an app/[lang] route segment so URLs start with /ar or /en, set the html lang and dir attributes in the layout, and store posts in separate language folders. Return hreflang alternates from generateMetadata, use CSS logical properties for mirrored layouts, and choose Arabic web fonts tested at mobile sizes. Check PDPL consent before adding newsletter forms or analytics.
Can non-technical writers use an MDX blog?
They can, with help. Writing Markdown is easy to learn, but committing files to Git is a barrier for many marketers. Common solutions are a Git-based CMS that edits MDX files through a web interface, a headless CMS feeding the same routes, or a simple review workflow where writers send drafts and a developer publishes them.
How much does it cost to build a Next.js blog?
A developer can build a basic single-language MDX blog in a few days. Agency budgets in the Gulf typically run SAR 8,000–20,000 for a production-ready single-language blog and SAR 25,000–60,000 for a bilingual Arabic and English blog with CMS integration and migration from WordPress. Static hosting is often free or costs little each month.
Ship your blog with Next.js and MDX
A blog with Next.js 15 and MDX takes an afternoon to scaffold and a few days to polish: configure @next/mdx, add mdx-components.tsx, export metadata from posts, await params in dynamic routes, and pre-build every slug. Then add metadata, a sitemap, JSON-LD and highlighting before launch.
For Saudi teams, plan Arabic RTL routes and hreflang from the start. The result is a fast, secure, low-cost blog that ranks well and gives your writers room to publish interactive content.
Want a bilingual Next.js blog built for you?
We build and migrate Arabic and English MDX blogs with SEO, schema and fast hosting included. Get a fixed quote in SAR.
Get a free consultation See pricingWant this handled for you?
We build and optimise sites like this every week. Book a free 30-minute call and we will tell you exactly what we would change on yours.
Get a free site review