In the last few years, I've developed multiple sites using Next.js as a starting point. Each time, when it comes to accelerating the marketing output, WordPress has been the go-to solution that people in marketing ask for.
The trouble is, you might have developed a whole bunch of pages in your Next.js site, and rebuilding them all in WordPress would be a pain. This is especially true if you're building a more dynamic app that uses the isomorphic features of Next.js to its fullest.
Some non-optimal ways of integrating Next.js and WordPress
There are various different techniques for how you can separate/combine the Next.js part and the WordPress part, but they often have significant downsides. I'll outline some of them here, assuming you begin with a Next.js site at myapp.com.
Option 1 – Host your sites on separate subdomains
For instance, you might host your WordPress instance at blog.myapp.com.
The benefit of this is that it's relatively simple to do. Set up your WordPress instance, configure your DNS, change the WordPress URLs in config and you're done.
There are quite a few downsides to this, though:
- What if the content you want to make doesn't neatly fit into a named subdomain? What if it's some blogs, some landing pages, some support pages, etc.?
- Many major analytics platforms, including Google Analytics, are fiddly to set up to work across subdomains.
- Subdomains have SEO implications that you may not want to take on.
Option 2 – Serve your WordPress site in a subdirectory
There are a couple of ways to do this. One way is to install WordPress in a subdirectory, and then use something like Cloudflare Page Rules to route certain requests to the WordPress instance.
You could also use Rewrites in Next.js to proxy requests to a subdirectory through to the WordPress site.
The downsides here are:
- It's a fiddle to maintain as you have to update either your CDN rules (assuming you even have a CDN that allows it) or the Next.js rewrites if you want to add more subdirectories.
- I'm not even sure how you configure WordPress to respond to multiple subdirectories.
- If you don't want to add new subdirectories, you could namespace everything under one directory, like /blog, but that has the same issues as Option 1 where the content may not make sense like that.
What's the best way then?
Well, I'm glad I asked.
First of all, here are the benefits of the solution you're about to see:
- There is no maintenance overhead. New pages added to WordPress just show up immediately, just like you expect. New pages added to Next.js work just like you expect (even if that content is dynamic and CMS-driven itself).
- You're not forced into a subdomain or subdirectory structure that doesn't match your content.
- Your analytics remain intact, and track sessions across the Next.js/WordPress boundary without any extra configuration.
- You don't have to take unwanted SEO implications.
And the best thing is, the setup is really simple.
Step 1 – Make sure your WordPress is publicly available with a DNS entry
For this step, it's a good idea to set up your WordPress site to be accessible at a subdomain. This won't be the public-facing domain you use, so don't worry about the subdomain downsides I mentioned above. You'll also use this subdomain to log into the admin part of WordPress.
Let's assume you've set up a DNS entry for your WordPress site at wordpress.mysite.com.
Step 2 – Set up Next.js to defer to your WordPress site, if there is no matching content in the Next.js site
This is one of the key parts of all this – and the magic that makes the maintenance-free setup possible.
To do this, you'll need to add a fallback block to your Next.js rewrites, like this:
// next.config.js
module.exports = {
async rewrites() {
return {
fallback: [
{
source: "/:path*",
destination: `https://wordpress.mysite.com/:path*`,
},
],
}
},
}
This piece of config says that if a request comes in that doesn't match any of the static files or dynamic routes in the Next.js app, it should defer that request to the WordPress site, preserving the rest of the URL.
Step 3 – Configure WordPress to render pages on the root domain
You'll need to modify wp-config.php and add the following lines:
define('WP_SITEURL', 'https://wordpress.mysite.com');
define('WP_HOME', 'https://mysite.com');
define('COOKIE_DOMAIN', '.mysite.com');
WP_SITEURL tells WordPress to serve all the site's assets from the subdomain. This is also the base URL of the admin pages.
WP_HOME tells WordPress that the base URL for all the links it renders should be the root domain.
COOKIE_DOMAIN tells WordPress that cookies set on all subdomains and the root domain are shared. This is so your login cookie persists across domains.
Step 4 – Fix weird stuff
WordPress does a whole load of really complicated, weird stuff that I'm sure there are good reasons for, but the behaviour is...somewhat surprising. We have to now stop WordPress doing some stuff that breaks under this configuration, and fix a few other bits.
The remaining changes all happen in wp-content/themes/your-theme/functions.php.
First, add the following lines.
remove_filter('template_redirect','redirect_canonical');
This prevents WordPress from redirecting your site to what it thinks is the "correct" URL for each page. For reasons that I don't understand, instead of using your site's WP_HOME value as the base URL for canonicals, it appears to use the domain that the request came in on as the base URL. For our config, that means it would redirect all page views to wordpress.mysite.com/whatever but that's exactly what we don't want it to do, so we remove this WordPress "filter".
Second, we need to change the base URL of the WordPress REST API to be the subdomain.
add_filter('rest_url', 'serve_rest_url_on_wp_subdomain');
function serve_rest_url_on_wp_subdomain ($url) {
return str_replace('https://mysite.com', 'https://wordpress.mysite.com', $url);
}
Note: You can probably improve this with a RegEx to ensure it only replaces at the start of the string, and uses the global WP_HOME and WP_SITEURL variables we set in config rather than hardcoding the URLs.
The reason you need this is that the WordPress backend uses the REST API to create and update content. However, as you'll access the admin pages on the subdomain, you need the REST API to be served from the same domain to avoid CORS errors.
Lastly, if you are using Yoast for SEO (it's very common, apparently), you need to change the URL that it renders in canonical link tags in your pages.
add_filter( 'wpseo_canonical', 'yoast_seo_canonical_fix’ );
function yoast_seo_canonical_fix( $canonical_url ) {
return str_replace('https://wordpress.', 'https://', untrailingslashit($canonical_url));
}
There are two things going on here:
- Ensuring all the canonical URLs that Yoast renders are on the root domain. Again, this could probably be done better with a regular expression, and using the global config values instead of hardcoding. This won't change how your site renders in a browser, but it affects how search engines understand your site, so this is important.
- Removing the trailing slash from the canonical URL. Next.js removes trailing slashes with a redirect, and this just makes sure our canonical URL matches the final URL you'll end up on. It's not crucial but it's tidier.
Step 5 – Check it all works!
What you should now be able to do, to test this is:
- Go to a page you know doesn't exist in your Next.js app, like https://mysite.com/nothing-to-see-here and verify that you get a 404 from your Next.js app.
- Log into WordPress at https://wordpress.mysite.com/wp-admin/ (the trailing slash is important because of annoying WordPress redirects!) and save a new post with a slug of nothing-to-see-here.
- Now, try https://mysite.com/nothing-to-see-here again and if everything is working correctly, you will see your page.