Jump to content

How to structure Blog Posts, Categories, and Tags


ryangorley
 Share

Recommended Posts

Hey all,
I'm coming from WordPress land, where blog posts, categories, tags, and publish scheduling are baked into the platform. ProcessWire is so amazing and flexible, but I'm kind of unsure about best practice for handling this. I'm migrating a site over with 100 blog posts, so I don't want to have to do it twice. This is how I expect things to work:

  • Posts (pages) would be assigned to just one category (e.g. News, Guides, etc.)
  • Posts could have multiple tags (e.g. iPhone Backup, Cloud Storage, etc.), and these would be shared with other posts across categories.
  • An archive page would exist for each category (/blog/news/ or /blog/categories/news/) and each tag (/blog/iphone-backup/ or /blog/tags/iphone-backup/) listing all content.
  • A copy writer could ideally add their own tags and see other tags have been used inside of the tag selector on a post. Categories will be predefined.
  • A date can be selected for when the content becomes visible on the site, and a logged-in user can preview that content prior to that date.

Categories:

So I would imagine that the best way to handle categories would be to create sub pages of the blog page for each category and then create the posts themselves as children of the relevant categories, right? I'm mostly okay with that, though it's going to lead to longer URLs (/blog/news/this-is-a-somewhat-short-blog-post-title/). Not great for SEO, but acceptable if it's the best solution otherwise.

Tags:

This is the one I'm lost on. I haven't played with it yet, but I know there is a tag input field. I don't know if it is aware of existing tags so we don't get too much duplication (e.g. iphone, iPhone, Iphone, i-phone, etc.). I know I can figure that out, but if someone knows already and can share it would save me some time. The big question is how do I go about automatically generating archives of all the blog posts that share each tag? Tags need to be shared across categories, so the sub page method wouldn't work as a fallback. Someone must have figured this out, but I can't find anything documented.

Publish Date:

So I get that I can add a date field and build some logic into the queries to not show pages if today is prior to that value. I imagine I would use the $user ->isLoggedin() method to display the page content if today is prior to that date value as well. I don't love doing things this way because my sitemap (generated by SEOMaestro) is going to list these pages, and they are going to get indexed by crawlers as empty. I'm wondering if there is some better method for handling this functionality.

--

Thanks in advance for the advice here!

Link to comment
Share on other sites

Hi @ryangorley!

Not sure if you've taken at this old but still very relevant post:

 

1 hour ago, ryangorley said:

So I would imagine that the best way to handle categories would be to create sub pages of the blog page for each category and then create the posts themselves as children of the relevant categories, right?

I'd say the best way is the one that fits your use case and in that sense ProcessWire has no rules about categorization.

1 hour ago, ryangorley said:

The big question is how do I go about automatically generating archives of all the blog posts that share each tag? Tags need to be shared across categories, so the sub page method wouldn't work as a fallback. Someone must have figured this out, but I can't find anything documented.

The tags can exist under their own parent separate from the "News > Blog Item 1" tree branch:

/tags/tag-1

And be linked through the tags inputfield, it's pretty much the same case for categories.

To my own personal taste I always tend to go with an structure like this:

- 📑 News
-- 📋 Tags
---- 📗 Tag 1
---- 📗 Tag 2
-- 📋 Categories
---- 📗 Category 1
---- 📗 Category 2
-- 📘 News Item 1
-- 📘 News Item 2

I like this because it keeps everything in the context of the "News" for the editors, and also the following URL structures which makes sense to me (not so sure about SEO but have had no complaints lol):

/news/tags/tag-1
/news/categories/category-1
/news/news-item-1

The only part I'm not a huge fan of is that it requires a bit of code using hooks to keep the right sort underneath the "News" page, since the actual news items share a parent with the tag/categories parent, sorting the News children by published reversed, will (most likely) send the Tags and Categories to the bottom. But it ain't that  big of a  deal and if the problem arises to you I'll gladly share my solution. 

1 hour ago, ryangorley said:

Publish Date:

So I get that I can add a date field and build some logic into the queries to not show pages if today is prior to that value. I imagine I would use the $user ->isLoggedin() method to display the page content if today is prior to that date value as well. I don't love doing things this way because my sitemap (generated by SEOMaestro) is going to list these pages, and they are going to get indexed by crawlers as empty. I'm wondering if there is some better method for handling this functionality.

Also, to my own taste, I use a custom field for publish date, this lets me have more control over it and handle the visibility of the actual post with the hidden/unpublished status depending on what I want to achieve.

Now for archive pages i'd suggest you take a look at what @ryan did in this old but also still relevant blog profile:

https://github.com/ryancramerdesign/BlogProfile/blob/master/site-blog/templates/archives.php

In all fairness this is not the clearest example and it's one of those part you'd expect to be built by default in a CMS, but I always think these details are worth the effort as a sacrifice for the flexibility PW gives you in terms of data structure/relation.

 

  • Like 4
Link to comment
Share on other sites

Hi @ryangorley,

as @elabx mentioned:

11 hours ago, elabx said:

I'd say the best way is the one that fits your use case and in that sense ProcessWire has no rules about categorization.

and his proposed solution is one of many that you can use. Personally I'm building blog structure similar to the one that you are after, so I will try to share my setup.

Usually I have 5 page templates structured as follow:
(site.com)
-blog
--blog-category
---blog-post
-blog-tags
--blog-tag

with pages relations (parent - children) setup in the backend, so you can publish only specific page under specific parent,  In this setup your urls will looks like:

site.com/blog/category-name/post-name
site.com/tags/tag-name

"Tags" parent page is published under global home page - with status hidden so it is excluded from searches, menus etc.

It helps to shorten url, you can add tags directly on the page tree if you wish and by using "tags" field while editing posts.

Blog post have also own "published_date" date field, that is used to sort posts on the site, but also on the page tree in the backend. You can also use this date value in the selector to ommit rendering pages with future date on the archive page (I'm not sure about SEOMastero).

I have one global archive page that is rendered from: blog, category and tag pages to avoid repeating of code.

So my code in the templates looks like this (simplified):

blog.php

<?php namespace ProcessWire; 
$posts = $page->find('template=blog-post, limit=12, sort=-published_date');
?>
<main id="main" class="main">
  <div class="header">...</div>
  <?php if (wireCount($posts)) : ?>
  <?php wireIncludeFile('layout/archive/archive.php', array("posts" => $posts)); ?>
  <?php endif; ?>
  ...
</main>

blog-category.php

<?php namespace ProcessWire; 
$posts = $page->children('template=blog-post, limit=6, sort=-published_date');
?>
<main id="main" class="main">
  <div class="header">...</div>
  <?php if (wireCount($posts)) : ?>
  <?php wireIncludeFile('layout/archive/archive.php', array("posts" => $posts)); ?>
  <?php endif; ?>
  ...
</main>

blog-tag.php

<?php namespace ProcessWire; 
$name = $page->name;

// query posts and count
$posts = $pages->find('template=blog-post, tags=' .$name. ', limit=10, sort=-published_date');
?>
<main id="main" class="main">
  <div class="header">...</div>
  <?php if (count($posts)) : ?>
  <?php wireIncludeFile('layout/archive/archive.php', array("posts" => $posts)); ?>
  <?php else :  ?>
  <h1><?php echo sprintf(__('There is no articles tagged with: %1$s'), '<i>'. $name .'</i>'); ?></h1>
  <?php endif; ?>
  ...
</main>

Please note pages->find and page->children selector, and limits changes for each archive.

The layout/archive/archive.php file looks like this:

<?php namespace ProcessWire; 
/*
 * blog archive page
 *
 * @note this archive layout is used by the: blog, tag, category, author templates
 */
?>
<div class="blog">
	<div class="grid-3">
		<?php foreach ($posts as $key=> $post) : ?>
		<article class="article">
		....
		</article>
		<?php endforeach; ?>
	</div>
	<?php echo $posts->renderPager(); ?>
</div>

To avoid getting blog posts with future dates you may add to selectors "published_date<=today". 

Regarding to publish scheduling, You can leave blog posts with future date unpublished and use Processwire LazyCron https://processwire.com/docs/more/lazy-cron/ to make them published automatically when the time is right 😉

Regarding to the tags field, you just need to play with it, it has autocomplete featues etc. so it should work just fine.

 

Link to comment
Share on other sites

  • 2 weeks later...

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...