facebook_pixel

Get pro tips and hot takes or your money back 💸

WordPress SEO Without a Plugin?

WordPress SEO without a plugin is a topic I’ve seen floated quite a bit recently. It’s actually part of a growing trend caused by misinformation and half-truths. If you think that disabling your WordPress SEO plugin in favor of something like this is a good idea, I think you might be a bit misinformed.

Let’s break down the reasons why doing WordPress SEO without a plugin is a really lousy idea.

Code in Themes is No More Efficient than Code in Plugins

If you think that moving away from an SEO plugin is somehow going to improve your site performance, you’re wrong. Unless you’re using an SEO plugin that is poorly coded or loaded with bloated features, switching to a method that runs inside your theme’s functions.php file is not going to help anything.

Our friend Austin Gunter wrote a great post about plugins and site speed. In his post, he dispels some of the myths that have been floating around the WordPress community for years. Basically, the quality of the code is what matters. Where the code lives or how much of it exists are not the problems. If you’re worried about performance, make sure you’re using a high-quality SEO plugin.

If You Switch Themes, You’ll Lose Your SEO Settings

One of the primary reasons that SEO plugins exist is because SEO is not something that should be directly tied to the theme you’re using at the moment. Your WordPress theme should handle only the most basic functionality related to displaying your content. Themes are not meant to be applications and they’re not meant to do tons of complex stuff. That’s what plugins are for.

Most websites switch their themes fairly regularly. Design trends change, company goals change… lots of things change. If you load up your theme with a ton of custom functionality such as SEO or custom post types, it’s going to be that much more difficult (and expensive) to switch themes. Do you really want to create more work for yourself and waste money? No, I didn’t think so.

The Non-Plugin Solutions All Really Suck

So if you somehow get past the first two issues and decide you don’t really care about them, there’s still one big problem. All of the WordPress SEO without a plugin solutions being passed around right now are useless. This one in particular is completely ridiculous. Let’s go through the code and see what each bit actually does…

Meta Keywords? Are you F#)%ing Kidding Me?

That’s right. The first part of this non-plugin SEO solution is for adding meta keywords to your website. Awesome! Apparently we’ve gotten in a time machine and headed back to the year 2002. I wonder why nobody bothered to tell me. Not only that, if you don’t manually set your own batch of keywords, it’s going to pull every tag and category on your site and blast them into the meta keywords tag. Kick ass.

<?php
function basic_wp_seo() {
global $page, $paged, $post;
$default_keywords = 'wordpress, plugins, themes, design, dev, development, security, htaccess, apache, php, sql, html, css, jquery, javascript, tutorials'; // customize
$output = '';
// description
$seo_desc = get_post_meta($post->ID, 'mm_seo_desc', true);
$description = get_bloginfo('description', 'display');
$pagedata = get_post($post->ID);
if (is_singular()) {
if (!empty($seo_desc)) {
$content = $seo_desc;
} else if (!empty($pagedata)) {
$content = apply_filters('the_excerpt_rss', $pagedata->post_content);
$content = substr(trim(strip_tags($content)), 0, 155);
$content = preg_replace('#\n#', ' ', $content);
$content = preg_replace('#\s{2,}#', ' ', $content);
$content = trim($content);
}
} else {
$content = $description;
}
$output .= '<meta name="description" content="' . esc_attr($content) . '">' . "\n";
// keywords
$keys = get_post_meta($post->ID, 'mm_seo_keywords', true);
$cats = get_the_category();
$tags = get_the_tags();
if (empty($keys)) {
if (!empty($cats)) foreach($cats as $cat) $keys .= $cat->name . ', ';
if (!empty($tags)) foreach($tags as $tag) $keys .= $tag->name . ', ';
$keys .= $default_keywords;
}
$output .= "\t\t" . '<meta name="keywords" content="' . esc_attr($keys) . '">' . "\n";
// robots
if (is_category() || is_tag()) {
$paged = (get_query_var('paged')) ? get_query_var('paged') : 1;
if ($paged > 1) {
$output .= "\t\t" . '<meta name="robots" content="noindex,follow">' . "\n";
} else {
$output .= "\t\t" . '<meta name="robots" content="index,follow">' . "\n";
}
} else if (is_home() || is_singular()) {
$output .= "\t\t" . '<meta name="robots" content="index,follow">' . "\n";
} else {
$output .= "\t\t" . '<meta name="robots" content="noindex,follow">' . "\n";
}
// title
$title_custom = get_post_meta($post->ID, 'mm_seo_title', true);
$url = ltrim(esc_url($_SERVER['REQUEST_URI']), '/');
$name = get_bloginfo('name', 'display');
$title = trim(wp_title('', false));
$cat = single_cat_title('', false);
$tag = single_tag_title('', false);
$search = get_search_query();
if (!empty($title_custom)) $title = $title_custom;
if ($paged >= 2 || $page >= 2) $page_number = ' | ' . sprintf('Page %s', max($paged, $page));
else $page_number = '';
if (is_home() || is_front_page()) $seo_title = $name . ' | ' . $description;
elseif (is_singular()) $seo_title = $title . ' | ' . $name;
elseif (is_tag()) $seo_title = 'Tag Archive: ' . $tag . ' | ' . $name;
elseif (is_category()) $seo_title = 'Category Archive: ' . $cat . ' | ' . $name;
elseif (is_archive()) $seo_title = 'Archive: ' . $title . ' | ' . $name;
elseif (is_search()) $seo_title = 'Search: ' . $search . ' | ' . $name;
elseif (is_404()) $seo_title = '404 - Not Found: ' . $url . ' | ' . $name;
else $seo_title = $name . ' | ' . $description;
$output .= "\t\t" . '<title>' . esc_attr($seo_title . $page_number) . '</title>' . "\n";
return $output;
}

Oh Joy! Auto-Generated Page Descriptions!

So as we continue through this awesome code snippet, we find that now our site will be automatically generating descriptions for every piece of content on the site. In fact, all of your archive pages will be displaying duplicate descriptions pulled from your global site description in the WP Admin settings. If you know anything about SEO, you know that the meta description is one of the most important parts of your content. You need to hand craft these things to catch the attention of people in the SERPs.

Blasting out auto-generated descriptions with no ability to override them isn’t going to do anything for you. In fact, it’ll probably wind up hurting you. If you’re not going to bother writing unique descriptions, you may as well let Google generate them for you. I can guarantee you they’ve put more thought into it than this.

<?php
function basic_wp_seo() {
global $page, $paged, $post;
$default_keywords = 'wordpress, plugins, themes, design, dev, development, security, htaccess, apache, php, sql, html, css, jquery, javascript, tutorials'; // customize
$output = '';
// description
$seo_desc = get_post_meta($post->ID, 'mm_seo_desc', true);
$description = get_bloginfo('description', 'display');
$pagedata = get_post($post->ID);
if (is_singular()) {
if (!empty($seo_desc)) {
$content = $seo_desc;
} else if (!empty($pagedata)) {
$content = apply_filters('the_excerpt_rss', $pagedata->post_content);
$content = substr(trim(strip_tags($content)), 0, 155);
$content = preg_replace('#\n#', ' ', $content);
$content = preg_replace('#\s{2,}#', ' ', $content);
$content = trim($content);
}
} else {
$content = $description;
}
$output .= '<meta name="description" content="' . esc_attr($content) . '">' . "\n";
// keywords
$keys = get_post_meta($post->ID, 'mm_seo_keywords', true);
$cats = get_the_category();
$tags = get_the_tags();
if (empty($keys)) {
if (!empty($cats)) foreach($cats as $cat) $keys .= $cat->name . ', ';
if (!empty($tags)) foreach($tags as $tag) $keys .= $tag->name . ', ';
$keys .= $default_keywords;
}
$output .= "\t\t" . '<meta name="keywords" content="' . esc_attr($keys) . '">' . "\n";
// robots
if (is_category() || is_tag()) {
$paged = (get_query_var('paged')) ? get_query_var('paged') : 1;
if ($paged > 1) {
$output .= "\t\t" . '<meta name="robots" content="noindex,follow">' . "\n";
} else {
$output .= "\t\t" . '<meta name="robots" content="index,follow">' . "\n";
}
} else if (is_home() || is_singular()) {
$output .= "\t\t" . '<meta name="robots" content="index,follow">' . "\n";
} else {
$output .= "\t\t" . '<meta name="robots" content="noindex,follow">' . "\n";
}
// title
$title_custom = get_post_meta($post->ID, 'mm_seo_title', true);
$url = ltrim(esc_url($_SERVER['REQUEST_URI']), '/');
$name = get_bloginfo('name', 'display');
$title = trim(wp_title('', false));
$cat = single_cat_title('', false);
$tag = single_tag_title('', false);
$search = get_search_query();
if (!empty($title_custom)) $title = $title_custom;
if ($paged >= 2 || $page >= 2) $page_number = ' | ' . sprintf('Page %s', max($paged, $page));
else $page_number = '';
if (is_home() || is_front_page()) $seo_title = $name . ' | ' . $description;
elseif (is_singular()) $seo_title = $title . ' | ' . $name;
elseif (is_tag()) $seo_title = 'Tag Archive: ' . $tag . ' | ' . $name;
elseif (is_category()) $seo_title = 'Category Archive: ' . $cat . ' | ' . $name;
elseif (is_archive()) $seo_title = 'Archive: ' . $title . ' | ' . $name;
elseif (is_search()) $seo_title = 'Search: ' . $search . ' | ' . $name;
elseif (is_404()) $seo_title = '404 - Not Found: ' . $url . ' | ' . $name;
else $seo_title = $name . ' | ' . $description;
$output .= "\t\t" . '<title>' . esc_attr($seo_title . $page_number) . '</title>' . "\n";
return $output;
}

NoIndex All The Things! #Winning

This just keeps getting better. Choosing which content to noindex is a very delicate process and needs to be handled by someone who understands the site content, architecture, and audience. This is not something you want to just set and forget. I should also point out that this script has again completely ignored custom post types. If you use this, don’t be shocked if large segments of your site start to fall out of the index…

<?php
function basic_wp_seo() {
global $page, $paged, $post;
$default_keywords = 'wordpress, plugins, themes, design, dev, development, security, htaccess, apache, php, sql, html, css, jquery, javascript, tutorials'; // customize
$output = '';
// description
$seo_desc = get_post_meta($post->ID, 'mm_seo_desc', true);
$description = get_bloginfo('description', 'display');
$pagedata = get_post($post->ID);
if (is_singular()) {
if (!empty($seo_desc)) {
$content = $seo_desc;
} else if (!empty($pagedata)) {
$content = apply_filters('the_excerpt_rss', $pagedata->post_content);
$content = substr(trim(strip_tags($content)), 0, 155);
$content = preg_replace('#\n#', ' ', $content);
$content = preg_replace('#\s{2,}#', ' ', $content);
$content = trim($content);
}
} else {
$content = $description;
}
$output .= '<meta name="description" content="' . esc_attr($content) . '">' . "\n";
// keywords
$keys = get_post_meta($post->ID, 'mm_seo_keywords', true);
$cats = get_the_category();
$tags = get_the_tags();
if (empty($keys)) {
if (!empty($cats)) foreach($cats as $cat) $keys .= $cat->name . ', ';
if (!empty($tags)) foreach($tags as $tag) $keys .= $tag->name . ', ';
$keys .= $default_keywords;
}
$output .= "\t\t" . '<meta name="keywords" content="' . esc_attr($keys) . '">' . "\n";
// robots
if (is_category() || is_tag()) {
$paged = (get_query_var('paged')) ? get_query_var('paged') : 1;
if ($paged > 1) {
$output .= "\t\t" . '<meta name="robots" content="noindex,follow">' . "\n";
} else {
$output .= "\t\t" . '<meta name="robots" content="index,follow">' . "\n";
}
} else if (is_home() || is_singular()) {
$output .= "\t\t" . '<meta name="robots" content="index,follow">' . "\n";
} else {
$output .= "\t\t" . '<meta name="robots" content="noindex,follow">' . "\n";
}
// title
$title_custom = get_post_meta($post->ID, 'mm_seo_title', true);
$url = ltrim(esc_url($_SERVER['REQUEST_URI']), '/');
$name = get_bloginfo('name', 'display');
$title = trim(wp_title('', false));
$cat = single_cat_title('', false);
$tag = single_tag_title('', false);
$search = get_search_query();
if (!empty($title_custom)) $title = $title_custom;
if ($paged >= 2 || $page >= 2) $page_number = ' | ' . sprintf('Page %s', max($paged, $page));
else $page_number = '';
if (is_home() || is_front_page()) $seo_title = $name . ' | ' . $description;
elseif (is_singular()) $seo_title = $title . ' | ' . $name;
elseif (is_tag()) $seo_title = 'Tag Archive: ' . $tag . ' | ' . $name;
elseif (is_category()) $seo_title = 'Category Archive: ' . $cat . ' | ' . $name;
elseif (is_archive()) $seo_title = 'Archive: ' . $title . ' | ' . $name;
elseif (is_search()) $seo_title = 'Search: ' . $search . ' | ' . $name;
elseif (is_404()) $seo_title = '404 - Not Found: ' . $url . ' | ' . $name;
else $seo_title = $name . ' | ' . $description;
$output .= "\t\t" . '<title>' . esc_attr($seo_title . $page_number) . '</title>' . "\n";
return $output;
}

Oh Fantastic. Now We’re Automating Page Titles?

I’m seriously glad we’re at the end of this script because I don’t think I can take much more of this. First of all, WordPress will already generate readable page titles out of the box and slapping things like “Tag Archive” into them isn’t going to help you rank. Second of all, if you think that site-wide automated page titles and SEO belong in the same sentence, I think you need to read this. There’s a reason Yoast’s plugin puts so much emphasis on the page title. It’s pretty damn important.

All the options and features in the most popular SEO plugins aren’t there by happenstance or for shits and giggles. They exist because they need to be there in order for you to rank your content. Applying the same options to every page title on your site is even more insane than automating your meta descriptions. Not only do you need to write titles that grab attention in the SERPs, you need to make sure you’ve got your keyword phrase added correctly too.

<?php
function basic_wp_seo() {
global $page, $paged, $post;
$default_keywords = 'wordpress, plugins, themes, design, dev, development, security, htaccess, apache, php, sql, html, css, jquery, javascript, tutorials'; // customize
$output = '';
// description
$seo_desc = get_post_meta($post->ID, 'mm_seo_desc', true);
$description = get_bloginfo('description', 'display');
$pagedata = get_post($post->ID);
if (is_singular()) {
if (!empty($seo_desc)) {
$content = $seo_desc;
} else if (!empty($pagedata)) {
$content = apply_filters('the_excerpt_rss', $pagedata->post_content);
$content = substr(trim(strip_tags($content)), 0, 155);
$content = preg_replace('#\n#', ' ', $content);
$content = preg_replace('#\s{2,}#', ' ', $content);
$content = trim($content);
}
} else {
$content = $description;
}
$output .= '<meta name="description" content="' . esc_attr($content) . '">' . "\n";
// keywords
$keys = get_post_meta($post->ID, 'mm_seo_keywords', true);
$cats = get_the_category();
$tags = get_the_tags();
if (empty($keys)) {
if (!empty($cats)) foreach($cats as $cat) $keys .= $cat->name . ', ';
if (!empty($tags)) foreach($tags as $tag) $keys .= $tag->name . ', ';
$keys .= $default_keywords;
}
$output .= "\t\t" . '<meta name="keywords" content="' . esc_attr($keys) . '">' . "\n";
// robots
if (is_category() || is_tag()) {
$paged = (get_query_var('paged')) ? get_query_var('paged') : 1;
if ($paged > 1) {
$output .= "\t\t" . '<meta name="robots" content="noindex,follow">' . "\n";
} else {
$output .= "\t\t" . '<meta name="robots" content="index,follow">' . "\n";
}
} else if (is_home() || is_singular()) {
$output .= "\t\t" . '<meta name="robots" content="index,follow">' . "\n";
} else {
$output .= "\t\t" . '<meta name="robots" content="noindex,follow">' . "\n";
}
// title
$title_custom = get_post_meta($post->ID, 'mm_seo_title', true);
$url = ltrim(esc_url($_SERVER['REQUEST_URI']), '/');
$name = get_bloginfo('name', 'display');
$title = trim(wp_title('', false));
$cat = single_cat_title('', false);
$tag = single_tag_title('', false);
$search = get_search_query();
if (!empty($title_custom)) $title = $title_custom;
if ($paged >= 2 || $page >= 2) $page_number = ' | ' . sprintf('Page %s', max($paged, $page));
else $page_number = '';
if (is_home() || is_front_page()) $seo_title = $name . ' | ' . $description;
elseif (is_singular()) $seo_title = $title . ' | ' . $name;
elseif (is_tag()) $seo_title = 'Tag Archive: ' . $tag . ' | ' . $name;
elseif (is_category()) $seo_title = 'Category Archive: ' . $cat . ' | ' . $name;
elseif (is_archive()) $seo_title = 'Archive: ' . $title . ' | ' . $name;
elseif (is_search()) $seo_title = 'Search: ' . $search . ' | ' . $name;
elseif (is_404()) $seo_title = '404 - Not Found: ' . $url . ' | ' . $name;
else $seo_title = $name . ' | ' . $description;
$output .= "\t\t" . '<title>' . esc_attr($seo_title . $page_number) . '</title>' . "\n";
return $output;
}

Well, I hope that I’ve convinced you to stick with one of the popular SEO plugins instead of trying to implement a WordPress SEO without a plugin solution. I realize this post has been kind of harsh and I honestly hope that the original author isn’t too offended by it, but I think it was warranted.

If people start switching over to solutions like this it could hurt their business. Real people could lose real money and I think that’s worth getting a little upset about. If you agree or disagree with anything I’ve said, I’d love to hear your thoughts in the comments. I promise the next post will be less ranty. 🙂

Join thousands of others and get weekly articles on improving your website

26 Comments

  1. Keesha

    We ended up in a lot of trouble with our last
    SEO company, been doing a bit of research on it myself and next
    time I are probably going to employ temp staff to have
    a go
    p.s Never take guidance from people on the Warrior Forums
    lol

  2. Ifeanyi

    Good point, but sometimes we get confused on which particular SEO Plugins to you, which is actually the best not the media-hype ‘best’. I wish we can do away with all these seo plugins and get less confused.

  3. Mateusz

    Personally, I use the All in One SEO Pack and also recommend. The main thing is, however, a good setting plug

  4. Ron

    plugin will make the SEO setup easy for any one. Otherwise any one may mess with the theme while trying to manually input the information. This is where plugin reall helps. You can set up your wp site without much coding knowledge (most of us falls in this category). In case if you are a code pro, then you don’t have to worry about plugins.

  5. Jaswinder

    I am new to WordPress, only for three months. I have no idea, which plugin is good for Seo and meta description as well.

  6. Jana

    I am not getting. So all the plugins I install are sitting within a theme and not outside of it? So each time I am changing a theme, I lose all the work that went into plugins and have to start from a zero? I hope I got it wrong..

    1. Ryan Sullivan

      Hey Jana,

      If you’re using plugins for SEO, you have nothing to worry about. It’s only if you’re adding SEO information directly into your theme that you have to worry about losing that information when you change themes. Hopefully that clears things up! Thanks.

  7. Ryan Paul

    SEO without a plugin makes perfect sense if you use standard WP functions combined with PHP in a ‘Must Use’ plugin.

    Although perhaps not the particular instance you quoted.

    Those changes would survive any theme change.

    1. Rob Neu

      Hey Ryan,

      I agree there’s nothing wrong with making your own custom “Must Use” plugin for SEO if you know what you’re doing. I think most people would have trouble sorting everything out though, so it’s not something I would personally recommend in general.

  8. Chucho

    SEO plugin vs doing everything manually is like using a calculator for big numbers vs calculating in your head.

  9. dawesi

    Would love a plugin similar to this that grabs keywords (google staff still says they are relevant.. .just not you?) and also auto generates an editable one line description for the page from a summary it gathers…

    99% of current SEO plugins are really ‘ordinary’, and just as useless (as above) to the average wordpress user who doesn’t have hours to write SEO info.

    You would have thought there would be something decent by now, but no.

  10. Spook SEO

    WordPress plug-in has greater worth when making a website and adjusting the keywords, title, and other things. With the use of plug-in the making website easy, and if usecode it may occur lots of complications.

  11. Kyle Alm

    Those are some really great points. Some people don’t realize that plugins, if written correctly, shouldn’t take more resources than adding functions to the theme.

  12. Alok Patel

    Quite really amazing information shared. I think WordPress SEO has a great things to do. Just need to manage the site title, description and keywords.

  13. OriginalEXE

    In the “NoIndex All The Things!” section you mention that it completely ignored cpt’s, but they are actually taken care of with is_singular() check.

    Just wanted to point that out, solution still sucks.

    1. FAT Media

      I guess I could have phrased it a little better. They’ve only handled single CPT entries. Nothing has been done for custom post type archives, or custom taxonomy archives for that matter, only category and tag archives.

      1. OriginalEXE

        Oh, true, now I understand what you wanted to say 🙂

  14. Gary Jones

    The code is just wrong on so many levels. As well as everything Robert has mentioned, there’s also the smaller issues of code quality – lack of adherence to the WordPress Code Standards, very little documentation, and the fact it’s all one giant function doing multiple tasks. This should never have made it on to the original site.

  15. WP Site Care

    My head is still spinning. I can’t see straight. I don’t want to be a hater but I can’t possibly think of why adding that giant block of code to your theme functions file would be a good idea. Way too many management headaches as well as ways to screw things up. Thanks for breaking it down and showing us the proper way.

Leave a Reply

Your email address will not be published. Required fields are marked *