First of all set your permalink structure to /%postname%
.
After that add a rewrite rule to rewrite all request like example.com/blog/postname
to the appropriate query:
add_action('init', 'blog_front');
function blog_front() {
add_rewrite_rule('^blog/([^/]+)/?','index.php?name=$matches[1]','top');
}
Now you have to prevent the duplicate url, sending a 301 redirect when example.com/postname
is requested:
add_action('template_redirect', 'say_hello_to_google');
function say_hello_to_google() {
if ( is_main_query() && is_single() && ( empty( get_post_type() ) || (get_post_type() === 'post') ) ) {
if ( strpos( trim( add_query_arg( array() ), "https://wordpress.stackexchange.com/" ), 'blog' ) !== 0 ) {
global $post;
$url = str_replace( $post->post_name, 'blog/' . $post->post_name, get_permalink( $post ) );
wp_safe_redirect( $url, 301 );
exit();
}
}
}
Finally you have to create permalink for your post with ‘blog/’ in front:
add_filter('the_permalink', 'post_permalink_w_blog');
function post_permalink_w_blog( $link ) {
global $post;
if ( $post->post_type === 'post' ) {
$link = str_replace( $post->post_name, 'blog/' . $post->post_name, get_permalink( $post ) );
}
return $link;
}
Last thing to is flush rewrite rules and… test.
Edit
As @Milo pointed out in comments, all of this can be avoided by setting permalink structure to /blog/%postname%
and then use the param 'rewrite'
having 'with_front'
to false
for all CPTs and all taxonomies.
'rewrite' => array( 'with_front' => false )
The problem is that most times, in a average WP install with a lot of CPT and taxonomies, there are chances that some of them are registred via 3rd party plugins…
in that case if that plugins register the CPTs/taxonomies not using 'with_front' => false
it’s a problem and the solution posted above is the last chance.
In addiction I have to say that standard categories and tags are registered with ‘with_front’ = true, so using this alternative method also standard category and tag must be re-registered with with_front
param setted to false
.