Eltrac

極客死亡計劃

不尊重文字的独立博主,胡言乱语的小说家,兴趣使然的神秘学研究者,爱走弯路的半吊子程序员,不务正业的学生,品味小众的游戏爱好者,需要靠早晨一杯咖啡维持生命体征的废物。
twitter

Best Practices for Building Independent Blogs in the New Era through a Roundabout Approach

I can’t remember how many times I’ve rewritten the code for my blog, but I’m sure this is the last time (for a while). Anyway, it’s time to put a temporary end to my perfectionism, which has found no balance among customization, maintainability, and cost.

Reasons for Rewriting#

Due to my increasing dissatisfaction with the design of my old blog, I decided to completely redesign it. But before I did that, I suddenly realized that I hadn’t updated my blog in a long time, and the recent updates were mostly incomprehensible novels, most of which were directly copied from my work on another site— in short, I hadn’t taken the time to seriously write a blog in a long while. I believe it’s necessary to occasionally document my explorations in certain areas with longer texts; this is quite different from my daily records when keeping a journal, as documenting the essence of things rather than time helps me clarify my thoughts while deepening my memory.

I accidentally lost the habit of blogging largely due to the cumbersome process of updating a static blog. In my previous rewrites, I used pure Next.js or Svelte frameworks, which are considered "serverless" applications. Since there is no database, all blog posts are stored as Markdown files alongside the blog's source code. This means that when I need to update the blog, I first have to write the content in a Markdown file, and also write the necessary Front Matter at the beginning of the file to indicate the article title, creation date, tags, and other metadata (and I can never remember the format and property names, so I have to find an old file and copy it to the new one). Once the file is ready, I have to place it in the corresponding directory of the Git repository, run npm run dev locally to test for issues, and after confirming everything is fine, push it to GitHub and wait for Vercel to deploy the new version of the blog in the production environment. Then, if I need to modify certain errors in the article or make additional updates, I cannot do this on a mobile device; I need to open my computer, launch GitHub Desktop and VS Code, edit my content, test it, push it again, and wait for deployment.

Developers new to static blog development might find this interesting and geeky, but I quickly grew tired of it because sometimes I just want to write an article to convey some thoughts or simply record something, yet I have to open an interface that makes me feel like I'm about to start writing bugs, followed by a series of very "hacker" operations to publish my article. This is simply inhumane.

To maintain the good habit of blogging without compromising myself, I decided to rewrite a blog system that feels more comfortable to me.

My Approach#

I need a fully functional graphical blog management backend, but I don’t want to reinvent the wheel or use flashy CMS solutions to "kill a mosquito with a cannon." I just want a simple, easy-to-use content management program suitable for personal blogs.

The answer that fits this description is—Typecho.

However, the problem is that Typecho is a blog program written in PHP, with a monolithic front and back end. For someone like me who has enjoyed the comfort of writing front-end code in JavaScript, going back to PHP feels like a modern person living in a cave in the mountains; I would have to readjust to PHP and rewrite a blog theme. This is simply unacceptable to me.

I want to enjoy the convenience of traditional blog operations while not leaving behind the elegance and efficiency of modern front-end development. So the solution is clear—use a headless CMS while redesigning the blog's front end. But the problem arises again; most headless CMS solutions on the market are somewhat bloated, or they have too many features that I don’t need for the problem I’m trying to solve. However, while Typecho cannot be used as a headless CMS, its scale perfectly meets my needs.

So, I just need to find a way to turn Typecho into a headless CMS, and all problems will be solved.

Getting Started#

I easily found an existing plugin that provides RESTful APIs for Typecho. This way, Typecho can serve purely as a backend to provide data for my designed front end, and I only need to update the blog content in Typecho’s console.

Next, I just need to focus on the design of the front end.

Choosing Tools#

I decided to use Next.js, which I am familiar with, to write the front end because I plan to host the front end on Vercel, and Vercel clearly has better support for Next.js.

In terms of CSS-in-JS, I chose the recently popular Tailwind.css instead of writing each class by hand with SCSS. On one hand, the new version of Next.js supports Tailwind.css by default, saving me the time of configuring it myself; on the other hand, with React's support for modular development, each identical or similar element can be written as a component, making semantic CSS somewhat unnecessary. At this point, having a more convenient and faster method is obviously the best.

By the way, I haven’t used Next.js for a while; my previous blog (Isla) used Svelte. The new version of Next.js added a new page routing method, the App Router, distinguishing it from the previous Page Router. Logically, using the App Router is better, but I clearly haven’t caught up yet, so I continued to use the Page Router to write the blog. However, as long as it runs, that’s fine.

There’s no need to mention additional tools like the React Icons library.

Fetching Articles#

Using the getStaticProps() function provided by Next.js Page Router allows fetching data from the headless CMS before the page loads. Use fetch() to get API content, and remember to use the await keyword.

The RESTful-style API provided by the plugin can be directly parsed with JSON, and don’t forget to include the await keyword during parsing.

export async function getStaticProps() {
  const res = await fetch('https://blog.guhub.cn/api/posts')
  const posts = await res.json()

  return { props: { posts } }
}

After completing this, return the article list data as Props to the main function.

However, here I encountered a backend issue; after running the code normally dozens of times, I found that the front end only displayed the first five articles. The reason is that the plugin provides pagination for the API, with a default of five articles per page, which needs to be indicated in the URL Query with ?page= to specify which page is being viewed. However, my current design does not require pagination, so I used another method provided by the API to increase the number of articles displayed per page, which is a rather silly solution.

const res = await fetch('https://blog.guhub.cn/api/posts?pageSize=9999')

Displaying Articles#

From the data obtained from the backend, the important data is under data.dataSet, which contains the article's title, creation timestamp, CID, category, Slug, etc. Notably, the property named digest is linked to Typecho's settings; if set to display the full content of $this->content() on the homepage, digest will contain the full HTML string of the content rather than just a summary. This plugin does not specifically output a property for the full content in the article list API; if digest only outputs a summary, to get the full content, one must use unique properties like slug or cid to fetch more detailed article information from another path.

This is clearly a bit too cumbersome, so I decided not to change Typecho's settings and use digest as the full content. However, I still have the need to output the actual summary in the article list, which means I need to truncate a summary on the front end.

Here’s how I implemented it:


function stripDigest(digest) {
    // Remove empty lines and spaces
    digest = digest.replace(/\ +/g,"").replace(/[ ]/g,"").replace(/[\r\n]/g,"")

    // Remove titles
    digest = digest.replace( /<h.*?>(\S|\s)*?<\/h.*?>/g,"")
    
    // Look for the <!--more--> tag in the article content
    // If it exists, truncate the content before <!--more-->
    // If it doesn't exist, truncate the first 150 characters
    var moreTag = digest.search(/<!--more-->/)
    var sliceEnd = (moreTag>0) ? moreTag+2 : 150
    
    // Remove HTML tags, keeping only text content, then perform the truncation
    digest = digest.slice(0,sliceEnd).replace(/<\/?[^>]+(>|$)/g, "") + "......"

    return digest
}

The summary should be a continuous piece of text without line breaks and spaces, so we first remove these whitespaces; it’s also best to remove the title; then we look for the familiar <!--more--> tag, which is used to manually truncate the summary. If there is a <!--more--> tag, we use it as a boundary to truncate the preceding text as the summary; if not, we truncate the first 150 characters. Finally, we need to remove the tags from the HTML string, keeping only the plain text content.

If you take a close look at the code above, you might be puzzled by this segment:

var moreTag = digest.search(/<!--more-->/)
var sliceEnd = (moreTag>0) ? moreTag+2 : 150

Here, the variable moreTag indicates the index of the <!--more--> position. If it exists, the index will be greater than 0, and logically, it should be used directly as the index for the subsequent slice() method, but I added 2 here because—if I don’t add this 2, the truncation position will be incorrect.

It’s a classic problem; I don’t know why this code is written, but if it’s not written, the program runs into issues.

Although it still doesn’t run perfectly with the addition, it’s better than not adding it. I’ve never figured out why, and then I just gave up. Now that I think about it, the best approach would be to follow the logic of RESTful API design and directly obtain the summary provided by the server. I’ll leave this problem to fix later when I have time.

Page Design#

Being able to fetch article data and display it on the front end has already completed the basic functionality of the blog, and now it’s time for page design.

In the previous versions of my blog design, I was deliberately pursuing simplicity (a design style that has been overused). The page composition at that time consisted of a white background with black text, along with some similarly simple black line icons and some light gray blocks to simply delineate areas.

This design indeed gave me a refreshing feeling amidst the flashy websites and apps, but the problem is that such overly simple designs can easily become "kitsch," or more accurately, I was engaging in kitsch with a design style widely recognized by many pretentious bloggers. This style lacks novelty and personality, and looking back, it’s also the main reason I wanted to completely rewrite the front end of the blog.

I’ve forgotten what inspired me, but after struggling for several days, I came up with a new concept for the appearance of the new blog. I wanted a design that is simple and elegant, yet distinctive, with clear colors and innovative typography. After incorporating some newspaper headlines and collage elements, I first created a concept map on Figma.

Initial Design on Figma

During the subsequent implementation process, I made some adjustments, adding a texture similar to the pages of a grid notebook, gradually transforming it into its current form.

RSS Subscription#

In this era where not many people read blogs, and most updates are often slow, it’s necessary to provide readers who are willing to follow me with a way to subscribe, reminding them when I finally update.

At first, I thought this wouldn’t be difficult since Typecho itself provides an RSS feed. But then the problem arose; I placed the backend (Typecho) under the domain blog.guhub.cn, while the front end is at www.guhub.cn, and Typecho itself is not designed for a decoupled front and back end solution. Therefore, in the subscription feed provided by Typecho, all article links point to the domain blog.guhub.cn, not the www.guhub.cn that I’m currently using.

I thought I just needed to grab the RSS XML on the front end and replace all instances of blog.guhub.cn with www.guhub.cn. However, the designers of Next.js probably never anticipated that a fool would want to do such a thing; it cannot directly handle XML data, and I didn’t find a way to directly fetch page content. Logically, this should be feasible, but I didn’t want to spend extra time on this step, so...

npm i rss

I installed an RSS library and regenerated a subscription feed using the article data I obtained from the API.

export default async function generateRssFeed({ posts }) {
    const feedOptions = {
        //...
    }

    const feed = new RSS(feedOptions);

    posts.map((item) => {
        let post = parseBlogPost(item);
        feed.item({
            title: post.title,
            description: post.content,
            url: `${site_url}/blog/${post.slug}`,
            date: post.date,
        });
    });

    fs.writeFileSync('./public/feed/index.xml', feed.xml({ indent: true }));
}

Now, Next.js will generate an XML file as the RSS feed when needed. If you wish, you can subscribe to my blog using this link.

Others#

I won’t elaborate on the detailed implementation steps here. I still have features like category and tag pages to implement, and the design of the article list is somewhat too simplistic; these will be gradually added later. Just doing these features will keep me busy for a while, so I shouldn’t get bored and delete the entire blog anytime soon.

Additionally, if you find the front end of this blog uncomfortable, especially since I haven’t implemented a dark mode yet, you can read articles on the Typecho side, where I use the Matcha theme, which I also developed, and it offers a more complete functionality and a much better reading experience than the current blog.

Oh, right, I almost forgot; I named this project Taco, which is the word obtained by removing part of the phoneme in Typecho.

Filing and Website Acceleration#

Since the server on the Typecho side uses Tencent Cloud's domestic server, I finally filed the domain guhub.cn. However, the main reason is to use services like CDN and object storage to improve the blog's access speed.

I won’t go into the specific steps for ICP filing and public security filing. For CDN and object storage services, I’m using Upyun, which is a cost-effective choice for low-traffic independent blogs; I’ve only incurred less than a dollar in fees after more than half a month of use.

Website Ping Test Result

The nationwide green feels very comfortable.


The results of my recent blog tinkering are about these.

Just a side note: if you’re attentive, you’ll notice that the blog currently doesn’t have a friendly link page, which I will add as soon as possible. I plan to reopen friendly link applications and remove some links that are not frequently interacted with. I also have a preliminary idea for the future development of the blog. I might write a separate article to discuss these contents, so I won’t elaborate here.

Alright, thank you for reading this far, and I hope you’re doing well.

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.