"Back to the Beginning” — Combining WordPress and Next.js in Sitemap Challenges

Francis Gregori
3 min readNov 16, 2023

--

Hey, hey, Code Fellows!

For a long time, I’ve wanted to share my knowledge as a programmer and talk about the challenges we face daily. But I always wondered where to start and what I could be sharing.

Well, this week, I encountered a problem that took me ‘Back to the Beginning’. This return to my programming roots happened while working on redesigning a website with Next.js and reacquainting myself with WordPress, the tool that shaped my career over ten years ago. Here, I’ll share with you how this past experience with WordPress helped me solve a somewhat simple challenge, but one that required a few hours between documentation and research to effectively integrate sitemaps in an environment that combines headless WordPress and Next.js.

The challenge I recently faced was integrating WordPress in a headless format, and using Next.js for the front-end. The beauty of this integration is its simplicity: WordPress already has a well-structured REST API, but I chose to use GraphQL for a smoother experience. Installing the WPGraphQL plugin was all I needed to get started.

The necessary changes to the PHP code were minimal, mainly adjustments for some custom posts. This process reinforced my appreciation for the flexibility and power of WordPress, something I might detail more in another post.

But the real reason behind this post came from a challenge I faced just before taking the project online. I needed to ensure that the WordPress sitemap remained functional despite the project being hosted on different servers.

The solution? Some smart Yoast filters adjust the sitemap URLs to the main domain.

Filter used to change the domain in the main sitemap.

add_filter( 'wpseo_sitemap_index_links', function( $links ) {
$home_url = home_url();
foreach ( $links as $key => $link ) {
$links[$key]['loc'] = str_replace( parse_url($home_url, PHP_URL_HOST), 'MAIN_DOMAIN.com', $link['loc'] );
}
return $links;
});

Filter used to update the URLs of the internal sitemaps.

add_filter( 'wpseo_sitemap_url', 'custom_sitemap_url_filter', 10, 1 );
function custom_sitemap_url_filter( $url ) {
$home_url = home_url();
return str_replace( parse_url($home_url, PHP_URL_HOST), 'MAIN_DOMAIN.com', $url );
}

On the Next.js side, I created a kind of ‘proxy’ to fetch and display the WordPress sitemap on the main domain.

import { NextResponse } from 'next/server';

export async function GET({ url }: Request) {
const baseDomain = process.env.NEXT_PUBLIC_WORDPRESS_URL;

const match = url.match(/https?:\/\/(.[^/]*)(.*)/);
if (match) {
const [, , path] = match;
const newUrl = baseDomain + path;

try {
const xmlRes = await fetch(newUrl);
if (!xmlRes.ok) {
return new NextResponse(`Error fetching XML from ${newUrl}`, {
status: xmlRes.status,
headers: { 'Content-Type': 'text/plain' },
});
}

let xmlText = await xmlRes.text();

const xslMatch = xmlText.match(/href="([^"]+\.xsl)"/);
if (xslMatch) {
const xslUrl = new URL(xslMatch[1], baseDomain).href;
const xslRes = await fetch(xslUrl);
if (xslRes.ok) {
const xslText = await xslRes.text();

xmlText = xmlText.replace(
/<\?xml-stylesheet[^>]*\?>/,
`<?xml-stylesheet type="text/xsl" href="data:text/xsl;base64,${Buffer.from(
xslText,
).toString('base64')}"?>`,
);
}
}

return new NextResponse(xmlText, {
status: 200,
headers: { 'Content-Type': 'text/xml' },
});
} catch (error) {
return new NextResponse(`Error: ${(error as Error).message}`, {
status: 500,
headers: { 'Content-Type': 'text/plain' },
});
}
} else {
return new NextResponse(`Invalid URL: ${url}`, {
status: 400,
headers: { 'Content-Type': 'text/plain' },
});
}
}

This process also included injecting XSL for XML styling, overcoming cross-domain limitations, and ensuring a proper presentation of the sitemap.

The solution I presented here resulted from a significant amount of research, experimentation, and adaptation to the specifics of the project.

I’m aware that, in the vast programming universe, there must be other ways to approach this same challenge. Therefore, I invite you to share your own experiences and solutions in the comments. Let’s enrich our collective knowledge by discussing alternative approaches and learning from each other.

Conclusion

This project was a kind of time travel, making me revisit technologies that were essential at the beginning of my career. But, more than that, it showed me how to connect these past learnings to current needs.

I hope this post serves as an incentive for those on this continuous journey of learning and evolution in programming. Together, we continue to grow and adapt to the challenges that come our way.

If you have doubts, ideas, or similar experiences, don’t hesitate to share them in the comments. Let’s code!

--

--