Original Blog Post: https://www.davidma.co/blog/2025-11-14-host-your-blog-on-a-subdirectory
How to Host Your Blog on a Subdirectory with Cloudflare
In this guide, you'll learn how to host your blog on a subdirectory (e.g. example.com/blog) instead of a subdomain (e.g., blog.example.com). Every step here has been tested and verified to work.
Introduction
Hosting your blog on a subdirectory can improve SEO and enhance user experience.
Although there are a lot of articles that espouse the benefits of using subdirectories over subdomains, few resources that provide a step-by-step guide on how to actually set this up.
Why Host on a Subdirectory?
The benefits to hosting on a subdirectory is primarily to improve SEO.
There are a lot of other articles out there on this topic, but they all say something similar to the following:
Hosting your blog on a subdirectory is better for SEO because it consolidates your website's authority and ranking power.
Google has stated that they do not treat subdomains as a separate entity.
Despite what Google has stated, empiric data suggests that subdirectories outperform subdomains in search rankings.
If you want to maximize your SEO efforts, hosting on a subdirectory is the way to go.
If you want to learn more, you can read this article by ButterCMS: Blog Subdomain or Subdirectory? Hint: One is 40% Better.
My personal experience has been similar. When I moved a blog from a subdomain to a subdirectory, I saw a noticeable increase in organic traffic and search engine rankings. The increase happened after a few weeks. During that time, I did not release any new content and nor did I promote the blog.
Why Not Host on a Subdirectory?
The setup is more complex. Many blogging platforms and CMSs are designed to work on subdomains, and configuring them to work on a subdirectory can be tricky.
I've personally found the setup process to be quite time consuming. It's a tricky process and you have to follow the instructions carefully. After having previously changed a blog from a subdomain to a subdirectory, I've found difficult to justify the time to do it for taikohub.com.
If you still think it's worth your time, then read on.
Steps to Host Your Blog on a Subdirectory
Lets suppose you have two sites right now. One is example.com and the other is blog.example.com. You want to host the blog on example.com/blog instead of blog.example.com.
Lets also suppose your blog (blog.example.com) is a Next.js app hosted on Vercel and your main site (example.com) is a static site hosted on Render.
Although Vercel and Render are used as examples here, the steps are nearly identical for other hosting providers. You do not need configure anything for your hosting provider. Everything can be done from the Cloudflare Dashboard, and from the comforts of your text editor.
Important: Note that Cloudflare often changes their dashboard UI and routes. If you find that the steps here do not match what you see on your Cloudflare Dashboard, just use the search function in the dashboard to find the relevant section.
Step 1: Set Up DNS Records for the Main Site
First, set up the DNS records for your main site (example.com). Again, if you do not use Render, then follow the equivalent steps for your hosting provider. Generally this should be in their documentation.
Go to your Cloudflare Dashboard. Click into your domain, then SSL/TLS, then Overview. Then click "Configure".
Next, select "Custom SSL/TLS" then select "Full".
Go to DNS records in the sidebar by clicking on "DNS", then "DNS Records". Then click "Add Record".
Add the following DNS Records. Replace my-site.onrender.com with the service URL for your main site. If you have other applications such as an API, you can add those as well. Note that it's important you set the "Proxy status" to "Proxied". It's also important you do NOT add a wildcard record (eg. *.example.com).
| Type | Name | Target | Proxy status | TTL |
|---|---|---|---|---|
| CNAME | @ | my-site.onrender.com | Proxied | Auto |
| CNAME | www | my-site.onrender.com | Proxied | Auto |
| CNAME | api | my-api.onrender.com | Proxied | Auto |
Step 2: Set Up DNS Records for the Blog
Make sure your blog is already accessible on a subdomain (eg. blog.example.com).
Add another DNS Record for the blog. Replace cname.vercel-dns.com with the CNAME target provided by the hosting provider for your blog.
| Type | Name | Target | Proxy status | TTL |
|---|---|---|---|---|
| CNAME | example.com | my-site.onrender.com | Proxied | Auto |
| CNAME | www | my-site.onrender.com | Proxied | Auto |
| CNAME | api | my-api.onrender.com | Proxied | Auto |
| CNAME | blog | cname.vercel-dns.com | Proxied | Auto |
Step 3: Configure Your Next.js Blog
Ensure Correct Routing for Static Assets
Make sure that your Next.js blog's router points to / and not /blog. You should NOT have any routes that contain /blog. Edit the next.config.js or next.config.mjs file and add basePath: "/blog" to the config.
/** @type {import('next').NextConfig} */
const nextConfig = {
basePath: "/blog", // Add this line
images: {
remotePatterns: [
{
protocol: "https",
hostname: "imagedelivery.net",
},
],
},
redirects: async () => {
return [];
},
};
export default nextConfig;
Step 4: Add a Cloudflare Worker
Go to Cloudflare Dashboard. Click "Workers & Pages". Click "Create" then click "Create Worker".
For the purpose of this blog post, we'll go with the easiest option by selecting "Start with Hello World!". For production applications, consider using Git. It looks like the following. Lets name it blog-worker. Then click "Deploy".
// worker.js
/**
* Welcome to Cloudflare Workers! This is your first worker.
*
* - Run "npm run dev" in your terminal to start a development server
* - Open a browser tab at http://localhost:8787/ to see your worker in action
* - Run "npm run deploy" to publish your worker
*
* Learn more at https://developers.cloudflare.com/workers/
*/
export default {
async fetch(request, env, ctx) {
return new Response('Hello World!');
},
};
Now replace the code with the following:
export default {
async fetch(request, env, ctx) {
async function MethodNotAllowed(request) {
return new Response(`Method ${request.method} not allowed.`, {
status: 405,
headers: {
Allow: "GET",
},
});
}
// Only GET requests work with this proxy.
if (request.method !== "GET") return MethodNotAllowed(request);
// Get the URL that was just requested.
const url = new URL(request.url);
// Swap out the subdirectory with the subdomain to request the actual URL.
const originUrl = url.toString().replace(
'https://example.com/blog',
'https://blog.example.com/blog'
).replace(
'https://www.example.com/blog',
'https://blog.example.com/blog'
);
// Fetch the origin.
const originPage = await fetch(originUrl);
// Return the subdomain, as the subdirectory.
const newResponse = new Response(originPage.body, originPage);
return newResponse;
},
};
Change the URLs as needed. To save, click on the version ID hash (eg. b30983e0) then click "Apply".
To deploy the changes, go to the worker dashboard. Click "Deployments". Look under "Version History". Click "…" then "Deploy" on the latest version.
| Version ID | Created | Version & Git Message | Source | |
|---|---|---|---|---|
| vb29485e0 | 3min… | Update to … | Dashboard | "…" |
| vf859f2e0 | 2h… | Updated Script | Dashboard | "…" |
Step 5: Connect Next.js Site with Cloudflare Worker
Go to "Worker Routes" in the Cloudflare Dashboard sidebar. Click on "Add Route".
Add the following route for the blog content:
- Route: example.com/blog*
- Worker: blog-worker. This is the worker you just created.
Add another route for the static assets:
- Route: example.com/blog/_next/static*
- Worker: blog-worker
You should now be able to access your blog at example.com/blog. If this works, congratulations! You've successfully hosted your blog on a subdirectory using Cloudflare Workers.
Configure Search Engine Robots.txt in Your Next.js App
Now that you've successfully hosted the blog on the subdirectory, you need to make sure search engines don't index the subdomain. This is because the blog is already indexed on the subdirectory. If search engines index both, then you may run into SEO issues due to duplicate content.
Update your next.config.js or next.config.mjs file.
/** @type {import('next').NextConfig} */
const nextConfig = {
basePath: "/blog",
images: {
remotePatterns: [
{
protocol: "https",
hostname: "imagedelivery.net",
},
],
},
redirects: async () => { // Add this block
return [];
},
async headers() { // Add this block
return [
{
source: '/:path*',
headers: [
{
key: 'X-Robots-Tag',
value: 'noindex, nofollow',
},
],
},
];
},
};
export default nextConfig;
Now update your cloudflare worker.
export default {
async fetch(request, env, ctx) {
async function MethodNotAllowed(request) {
return new Response(`Method ${request.method} not allowed.`, {
status: 405,
headers: {
Allow: "GET",
},
});
}
// Only GET requests work with this proxy.
if (request.method !== "GET") return MethodNotAllowed(request);
// Get the URL that was just requested.
const url = new URL(request.url);
// Swap out the subdirectory with the subdomain to request the actual URL.
const originUrl = url.toString().replace(
'https://example.com/blog',
'https://blog.example.com/blog'
).replace(
'https://www.example.com/blog',
'https://blog.example.com/blog'
);
// Fetch the origin.
const originPage = await fetch(originUrl);
// Return the subdomain, as the subdirectory.
let newResponse = new Response(originPage.body, originPage);
// Remove "noindex" from the origin domain.
newResponse.headers.delete("x-robots-tag");
return newResponse;
},
};
Step 6: Verify Your Subdomain is Not Indexed
Open your app's deploy URL. This may look something like https://vercel.com/my-projects-30d8ek3n/my-blog/d934nfid9823sbsNgoMnOOnsiKxn.
Open the browser's Network tab in Developer Tools.
Check for existence of an "X-Robots-Tag" header. If it's not there, then your Next.js app is correctly configured to not be indexed.
Step 7: Verify Your Subdirectory is Indexed
Go to Google URL Inspection Tool.
Enter your subdirectory URL (eg. example.com/blog).
Confirm that it shows as indexed.
Top comments (0)