postlight / headless-wp-starter

🔪 WordPress + React Starter Kit: Spin up a WordPress-powered React app in one step
https://archive.postlight.com/labs/wordpress-react-starter-kit
GNU General Public License v2.0
4.48k stars 650 forks source link

Automatically redirect old post slugs #131

Open cr101 opened 6 years ago

cr101 commented 6 years ago

In WordPress when you change a 'post slug' in the Permalink then WordPress will automatically redirect a link containing an old slug to the new link with new slug and sets the status to 301 Moved Permanently. This is very useful especially when you change a post's slug after Google has already indexed that page otherwise you will end up with lots of 404s and that's probably not good for SEO and certainly not good UX.

Since the frontend is a headless WordPress app instead of a traditional php theme how can you redirect old post slugs on the frontend?

TylerBarnes commented 6 years ago

Does WordPress do this? I've always had to use the redirection plugin to achieve this functionality.

cr101 commented 6 years ago

@TylerBarnes WordPress redirects old post permalink slugs automatically out of the box. To test it, first deactivate the redirection plugin, then create a new post, publish it and then view it. Then change the post's permalink slug to something else, save the post and then refresh the post and you will notice that WordPress has automatically redirected you to the new link.

WordPress saves the old post slug in the wp_postmeta table in the _wp_old_slug field.

I've been working with WordPress for years and I've never heard of that redirection plugin.

TylerBarnes commented 6 years ago

Weird, I just tried it and all I get is a 404. Do I have to enable something in functions.php to make it work?

cr101 commented 6 years ago

@TylerBarnes no need to add anything.

TylerBarnes commented 6 years ago

Hmm, I tested it on a few sites I've already built as well as a fresh install with the 2017 theme and it doesn't do it on any of them. I do see that it's referenced on a lot of sites on google but I can't find anything in the wordpress docs about it which is weird. Anyway I found your issue on gatsby. It seems like it would be pretty easy to set this up using your plugin + a little bit of logic.

modelm commented 6 years ago

I've been looking into this and learned WordPress redirects post slugs if you make an API request using ?filter[name]:

/wp-json/wp/v2/posts?filter[name]=sample-post

But, it doesn't redirect when you use ?slug:

/wp-json/wp/v2/posts?slug=sample-post

This seems like an upstream bug... The issue is compounded by the fact that this project uses a custom route for posts, /wp-json/postlight/v1/post?slug=sample-post and that route requires ?slug.

Ultimately, this project should abandon the custom post routes & use the built-in routes with ?filter to handle redirects automatically.

ETA Actually I had forgotten filter depends on the plugin wp-rest-filter since 4.7, so that will introduce another dependency. I'll try to find a way to make this work without the plugin first.

See also https://github.com/WP-API/WP-API/issues/1776 & https://github.com/WP-API/WP-API/issues/576

cr101 commented 6 years ago

@TylerBarnes The permanent redirect occurs in wp_old_slug_redirect() which is in query.php

modelm commented 6 years ago

First of all, I was incorrect thinking filter would redirect - my test was invalid, neither filter nor slug redirect in the vanilla WP API.

After reading the commentary in those issues I linked earlier, it seems like this problem is not as straightforward as I first thought. The API devs don't feel it's the responsibility of the API to handle it, and every frontend app could handle it differently. That said, I tried to make it work using as little code as possible. Here's a proof-of-concept:

https://github.com/postlight/headless-wp-starter/compare/master...modelm:fix-slug-redirect

This changes the frontend so that it uses the built-in /wp-json/wp/v2/posts?slug= endpoint. It also adds a filter that makes the API send a 301 redirect to the new slug if there was no result for the initial query and a post with that slug exists. This only redirects the API response, so the frontend displays the rewritten post, but the URL is still the old slug. Having dug into this problem this far, I'm not convinced this needs to be included in this project at this point. I'd like to see what other solutions arise from similar projects or if the WP API devs change their mind and decide to support this directly in the API itself first.

cr101 commented 6 years ago

@modelm If your frontend app is hosted on a different server than your WordPress CMS then it doesn't make sense to do the permanent redirect in the WP REST API.

The post permalink redirect needs to occur in the frontend and set the headers to 301 to let the Google bot know that the old link has been permanently redirected to the new link. For redirecting in getInitialProps have a look at https://stackoverflow.com/questions/45099395/conditional-redirection-in-next-js and https://github.com/zeit/next.js/wiki/Redirecting-in-%60getInitialProps%60

modelm commented 6 years ago

I agree the frontend should redirect. What's still unclear (IMO) is what the API should do to indicate this. If it does redirect, the frontend can recognize that and also redirect the client - so my fix-slug-redirect is a partial solution, although I am still hoping someone comes up with a standard way to handle this type of thing before everyone implements their own method.

cr101 commented 6 years ago

@modelm One solution would be that when you send a request to the REST API to fetch data for the post 'hello-world' but the name/slug for that post is now 'hello-world-new' then we could extend the WP REST API to find the post by old slug (see function _find_post_by_old_slug) and return that post data instead of nothing.

For example, a user clicked on the website link www.example.com/post/hello-world in the Google search results but that post's permalink slug has since changed and it's now www.example.com/post/hello-world-new then in getInitialProps() in frontend/pages/post.js we should check if the post_name in the post data returned by the REST API still matches the context slug. If not then we do the 301 permanent redirect to www.example.com/post/hello-world-new

I hope this makes sense.

Emiliano-Bucci commented 6 years ago

@modelm Hi, did you come up to a solution to this?

modelm commented 6 years ago

@Emiliano-Bucci See https://github.com/postlight/headless-wp-starter/compare/master...modelm:fix-slug-redirect for a proof-of-concept. The API sends a 301 if 1) it would otherwise 404 and 2) it finds a post that exists with the requested slug using a hack filter that shortcircuits wp_old_slug_redirect.

Emiliano-Bucci commented 6 years ago

@modelm Thanks for the answer. The fact is that i'm not using postlight (i'm building the front end of an already existing website made it with wordpress, with next js and similar stuff that you find in postlight). So i think i just have to implement that, right?

modelm commented 6 years ago

If you add that filter to your wordpress install it will redirect API requests when there's an old slug match. It's up to your frontend to process/follow that (in the react app included in this repo, that's automatic). For example - you probably want to forward a 301 to the client and redirect them too, otherwise they'll never know the slug has changed.

Emiliano-Bucci commented 6 years ago

@modelm Thanks again! Yes, i think that i will have to handle this thing. I'm using wp-graphql, i will see if someone implement something related to this, thanks very much for your time :)

aslak-dirdal commented 5 years ago

Will there ever be a fix in Wordpress for this?