Creating a Blog - 14. Classifying Posts Using Tags
- blog
Blog Creation Series
Title | Link |
---|---|
1. Basic Setup | https://witch.work/posts/blog-remake-1 |
2. HTML Design for the Main Page | https://witch.work/posts/blog-remake-2 |
3. Structure Design for Post Detail Page | https://witch.work/posts/blog-remake-3 |
4. Enabling Relative Path for Images | https://witch.work/posts/blog-remake-4 |
5. Improving Minor Page Composition and Deployment | https://witch.work/posts/blog-remake-5 |
6. Design of Page Element Layout | https://witch.work/posts/blog-remake-6 |
7. Main Page Component Design | https://witch.work/posts/blog-remake-7 |
8. Post List/Content Page Component Design | https://witch.work/posts/blog-remake-8 |
9. Automatic Generation of Post Thumbnails | https://witch.work/posts/blog-remake-9 |
10. Design Improvements for Fonts, Cards, etc. | https://witch.work/posts/blog-remake-10 |
11. Adding View Counts to Posts | https://witch.work/posts/blog-remake-11 |
12. Page Theme and Post Search Functionality | https://witch.work/posts/blog-remake-12 |
13. Improvements to Theme Icons and Thumbnail Layout | https://witch.work/posts/blog-remake-13 |
14. Changing Post Classification to Tag-Based | https://witch.work/posts/blog-remake-14 |
Optimization of Main Page Processing | https://witch.work/posts/blog-opt-1 |
Creating Pagination for Post List | https://witch.work/posts/blog-opt-2 |
Uploading Images to CDN and Creating Placeholders | https://witch.work/posts/blog-opt-3 |
Implementing Infinite Scroll on Search Page | https://witch.work/posts/blog-opt-4 |
1. Why Classify Using Tags
The tasks I have documented so far include blog image optimization, inserting an introduction page, linking to the board title, adding post thumbnails, pagination of post lists, dark mode, chronological order of posts, adding view counts, creating a TOC automatically, comments functionality, search functionality, tag filtering, and SEO.
Except for the tag filtering, the rest are complete. I had postponed the tag filtering as it didnβt seem necessary with the existing search functionality.
However, as the number of posts increased, classifying them solely by the previously considered categories of CS, development, and others became increasingly difficult. While I anticipated the development category would grow, I didn't expect it to expand this much.
Therefore, I decided to classify posts using tags that reflect themes more accurately. Since posts already had tags applied, I could utilize this functionality.
2. Concept
However, classifying posts via tags proved to be a more complex task than I anticipated.
If it were simply a matter of viewing posts by tag on the list page, it would have been straightforward, but it required changes to the site structure and several components.
Currently, there are about 15 different tags. If I remove the random tags I previously added, there will be around 10 remaining. Given potential future growth, I expect it won't exceed two digits.
However, even currently, displaying all tags for posts exceeding 10 in the header is nearly impossible for visibility.
Thus, I aim to transition from a folder-based classification of posts to a tag-based classification, envisioning how the site should evolve accordingly.
While I need to be mindful of my tagging practices, I believe this system is more flexible, allowing me to easily create new tags if needed.
Currently, posts are classified and displayed based on folders, sorting the posts within each folder chronologically. I need to transform this to a tag-based classification instead. The overall concept is illustrated as follows.
2.1. Abandoned Concept
Initially, I considered combining the search page and tag filtering page into a single page that would display only posts filtered by the search term and tags. This method is employed in gatsby-starter-lavender.
However, I opted against this for several reasons.
First, there is a performance and implementation difficulty difference. If filtering by tags and search terms is handled on a single page, it wonβt be possible to generate pagination for the filtered results ahead of time.
Thus, the page number and search term must be managed through query strings to display filtered results based on those values. For example, /tag?search=searchTerm&page=2
.
However, this means that every time a search term is entered, the following process must occur:
Filter posts from all posts based on search term -> Paginate the resulting posts and display them
This would inherently perform worse than serving a statically generated page (such as through URLs like /[tag]/[page]
).
Second, I believe combining search, tag filtering, and pagination doesn't result in a good UX. In my current implementation, the search updates results as the user types into the search field. Although it's not entirely real-time, debouncing optimization is applied.
This approach allows users to see search results without performing any extra actions after entering a search term, reducing interaction with the user.
However, pairing this real-time search filtering with pagination does not work ideally. For instance, if 100 posts are found when typing the search term A
, resulting in 10 pages, and the user navigates to page 9 (?search=A&page=9
), adding B
to the search term changes the results to only 25 posts.
So, what should be displayed on page 9 of the search results for AB
? Show no search results? Reset the search term? Or direct the user to the last page of the current search results? While any option is possible, they all involve changing the current URL and page number, which would confuse the user.
If I used infinite scroll as in the previous search page, it would be comfortable to display search results. However, I could not abandon the advantages of pagination that gives users a sense of control.
Moreover, I lack expertise in UX design, making it difficult to create a new method that combines real-time search, tag filtering, and pagination advantages. Thus, I've resolved to design the page routes in the manner described.
2.2. Page Structure
Currently, /posts/[category]
displays the list of categorized posts, and /posts/[category]/[slug]
is for the detailed view of each post. Additionally, /posts/[category]/page/[page]
displays paginated results for categories with more than one page.
Considering multiple tags are allowed, using URLs like /posts/[tag]/[slug]
is not ideal. Given the current URL naming convention, the likelihood of having duplicate folder names with different tags is low, so the detailed post pages will retain the format /posts/[slug]
.
I did not structure the pagination URLs as /posts/[category]/page/[page]
simply because it seemed neat. Rather, having the intermediate section page
helps to avoid a situation where the detailed post URL format (/posts/[category]/[slug]
) and paginated URLs (/posts/[category]/[page]
) clash. It is discouraged to have two dynamic routes, hence the introduction of the intermediate page
section.
However, since a tag-based categorization does not involve such dynamic route overlaps, for pages with more than one page based on tags, the format will be /posts/tag/[tag]/[page]
. A separate route to show all posts will be created as /posts/all
and /posts/all/[page]
.
Consequently, the structure within src/pages/posts
will be organized as follows:
posts
βββ all
β βββ [page]
β β βββ index.tsx
β βββ index.tsx
βββ tag
β βββ [tag]
β β βββ [page]
β β β βββ index.tsx
β β βββ index.tsx
βββ [slug]
β βββ index.tsx
βββ index.tsx (Post Search Page)
3. Extracting Posts
3.1. Paginating All Posts
Previously, there was no pagination for all posts. The only way to view all posts was through the search page, which had pagination only by category. Therefore, I will create a separate function to paginate through all posts. This will require editing src/utils/post.ts
.
I can utilize the existing pagination function for posts nearly as is.
3.2. Paginating Posts by Tag
Each post's tags exist as an array, post.tags
, within the elements of allDocument
located in contentlayer/generated
. Therefore, let's extract all of these.
Since the JS Set object can iterate in the order the elements are inserted, extracting tags from getSortedPosts
will yield the most recently authored post tags in order.
To extract all tags from posts, create src/utils/postTags.ts
and write the function.
Next, let's create a function to extract posts with a specific tag. The existing getCategoryPosts
function operates as follows, requiring PageInfo
information to load only the necessary number of posts.
Based on this, create a similar function called getPostsByPageAndTag
. It will also need to paginate posts with the corresponding tag, and therefore will require the same information.
4. Creating Page Structure
Let's implement the previously established page folder structure. First, move all posts to posts
rather than categorizing them inside folders like posts/cs
.
4.1. Constructing Tag-Based Pages
First, let's create the pages that classify posts by tags. Edit src/pages/posts/tag/[tag]/index.tsx
.
Use getAllPostTags
to provide possible paths for all tags.
Then, based on the created paths, use getPostsByPageAndTag
to fetch the posts associated with that tag in getStaticProps
.
The page component should now utilize the generated tag
and tagURL
.
Now edit the file responsible for pages exceeding two pages: src/pages/posts/tag/[tag]/[page]/index.tsx
. It should first generate pages based on the tags in getStaticPaths
, ensuring to retain ISR for existing functionality.
In getStaticProps
, implement retrieval of posts for those dynamically created pages:
Like before, ensure the page component uses tag
and tagURL
.
4.2. All Posts Page
In addition to the tag classification pages, an all-inclusive posts page should also exist. Several blog templates, such as Jbee's blog or gatsby-starter-lavender, include an "All" tag classification.
The pages responsible for this will be explicitly created as posts/all
and posts/all/[page]
. Since this isnβt categorized by tags, I believe using a /tag
URL in this context is inappropriate. Constructing explicit routes helps to avoid dynamic route overlaps. One should ensure not to generate a URL path of all
when writing posts.
First, create src/pages/posts/all/index.tsx
. This will follow a structure similar to the tag pages. We can create getStaticProps
as follows and construct the component accordingly.
The detailed page's getStaticProps
, like before, includes redirecting to /posts/tag/all
when on page 1, and ensuring no posts are found leads to a "not found" response:
4.3. Post Detail Page
The existing detailed post page located at src/pages/posts/[category]/[slug]/index.tsx
should be moved to src/pages/posts/[slug]/index.tsx
. Now letβs create the detail page based on this.
In getStaticPaths
, all post paths must be generated. Since every post now directly exists within the /posts
folder, post._raw.flattenedPath
will serve effectively as the slug.
The getStaticProps
should retrieve the relevant post information:
4.4. Main Page
To keep the main page concise, we will display only the most recent 9 posts, organized in rows of three.
5. Other Modifications
5.1. Header Modification
Let's modify the header structure, moving from folder-based categorizations. We will retain only the /posts/all
page, the search page, and /about
. Originally, I considered using icons for these, but it seems unnecessary. Remember, it will be possible to navigate to different tag classification pages directly from the post list page.
To update the header, we will modify blog-category.ts
, which receives navList
as props to display those menu items in the navigation bar.
5.2. Filtering by Tags
With the transition to tag-based classification, the categorization has significantly expanded. Though I plan to reduce clutter, I anticipate greater than just the previous three categories of CS, development, and others. This expansion indicates that listing all categories in the header is no longer feasible.
While itβs possible to structure the menu hierarchically, I believe overly strict classification reduces the purpose of the tag system. Thus, I explained in the header section that only /posts/all
remains in the header. Consequently, I will enable links from the post list page to other tag categorization pages.
Initially, I considered a dropdown menu for selecting tags. However, this could lead to additional interactions for users attempting to view post lists. From an accessibility standpoint, if the tagging classification is presented in a dropdown, screen readers might struggle to read it properly.
Hence, links for navigating to tag-based post categorization pages will be visible on post viewing pages. The end result will appear similar to the design outlined below:
While the design is not exceptional (as I am not a designer), there is no need to go into detail about CSS; I will only provide the core logic in code.
5.2.1. URL Conversion Function
The tag list will be fetched using getAllPostTags
, which simplifies aspects related to the function, leaving tag name handling for conversion to URL paths. For the "All" tag, it must lead to a special link, so it's treated distinctly.
Thus, I will create a function in src/utils/postTags.ts
to convert tag names to URLs:
5.2.2. Component Creation
Next, create the tag filter component in src/components/tagFilter/index.tsx
. What inputs does this component require?
Firstly, it needs the complete list of tags. Although this can be constructed internally, since tags are constants, I will define them in src/utils/postTags.ts
and pass them as props.
The currently selected tag should be defined through the URL, so it needs to be provided as props. Additionally, the function that generates page URLs for the tags must also be supplied as a prop. The makeTagURL
function we created previously serves this purpose.
Thus, the TagFilter
component may look like this:
This component can be integrated as follows, appearing on all pages related to post lists:
6. Plugin Correction
Due to adjustments in folder structure, the plugin responsible for copying post images in the pre-build phase needs updates. Modify src/bin/pre-build.mjs
to look like this:
7. Troubleshooting Duplicate Path Issues
After these changes, building the project resulted in the following error:
What could be the issue? The thumbnail creation and Cloudinary upload process seemed suspect, so I modified the function to only save thumbnails locally.
With this change, that particular error was resolved; however, a new error surfaced:
Upon researching GitHub issues, it was suggested that this may be due to duplicate routes. I suspected the presence of both HTML
and html
tags causing the issue.
Upon altering the html
tag to HTML
, the build successfully completed.
Using Set
to eliminate duplicates ensures that both HTML
and html
are treated as unique. Therefore, generating URL paths from src/pages/posts/tag/[tag]/index.tsx
ensures that duplicate tag strings lead to HTTP errors.
8. Reducing Tags
Since there are abundant ambiguous tags, I will narrow down the list to a few essential categories. The tentative list includes the following:
- OOP
- Javascript
- HTML
- Study
- React
- Web
- Front
- Tip
- CS
- CSS
- Algorithm
- Language
- Git
- Blog