Eltrac

極客死亡計劃

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

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

I can’t remember how many times I’ve rewritten the code for my blog, but I’m quite sure this is the last time (for a short 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#

Because I increasingly disliked the page design of my old blog, I decided to completely overhaul 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 some incomprehensible novels, most of which were directly copied from my creations on another site—in short, I hadn’t seriously sat down to write a blog post in a long time. I believe it’s necessary to occasionally document my explorations in certain areas with longer texts, which is quite different from my daily records when keeping a journal. Writing about the subject itself 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 was no database, all the blog posts were stored as Markdown files alongside the blog's source code. This meant that when I needed to update the blog, I had to first write the article content in a Markdown file and also write the necessary Front Matter at the top of the file to indicate the article title, creation date, tags, and other metadata (and I could never remember the format and attribute names, so I had to find an old file and copy it into the new one). Once the file was ready, I had to place it in the corresponding directory of the Git repository, run npm run dev locally to test for issues, and after confirming there were none, push it to GitHub and wait for Vercel to deploy the new version of the blog in the production environment. After that, if I needed to modify certain errors in the article or make additional updates, I couldn’t do these operations on a mobile device; I needed to open my computer, launch GitHub Desktop and VS Code, edit my content, test it, push it, and then wait for deployment.

Developers new to static blog development might find this interesting and very geeky, but I quickly grew tired of it because sometimes I just wanted to write an article to convey some thoughts or simply record something, yet I had to open an interface that made me feel like I was 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 while not compromising on my comfort, I decided to rewrite a blog system that felt more comfortable to me.

My Approach#

I needed a fully functional graphical blog management backend, but I didn’t want to reinvent the wheel or use a flashy CMS that would be overkill for my needs; I just wanted 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, which integrates both front and back ends. 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 moving into a cave in the mountains; I would have to readapt to PHP and rewrite a blog theme. This was simply unacceptable to me.

I wanted to enjoy the convenience of traditional blog operations while not wanting to leave behind the elegance and efficiency of modern front-end development. So the solution was clear—use a headless CMS while redesigning the blog's front end. But then the problem arose again: most headless CMS options 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 just meets my needs.

So, I just needed to find a way to turn Typecho into a headless CMS, and all problems would 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 needed to focus on the design of the front end.

Choosing Tools#

I decided to use Next.js for the front end because I planned to host it 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, so having a more convenient and faster method is obviously the best.

By the way, I hadn’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, distinct from the previous Page Router. Logically, using the App Router is better, but I clearly hadn’t caught on 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, remembering 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, I encountered a backend issue here; 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 includes the article's title, creation timestamp, CID, category, slug, etc. Notably, there is a property called digest, which is linked to Typecho’s settings; if it is set to display the full $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 text, one would need to 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 needed to output the actual summary in the article list, which means I needed to truncate a summary in 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 before the <!--more--> content
    // If not, 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 I first remove these whitespaces; it’s also best to remove the title; then there’s the familiar <!--more--> tag, which is used to manually truncate the summary. If there’s a <!--more--> tag, it serves as a boundary to truncate the preceding text as the summary; if not, truncate the first 150 characters. Then I 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 find this part puzzling:

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, not adding it causes even bigger problems. I’ve never figured out why, and then I just gave up. Now I think the best way to handle it is to follow the logic of RESTful API design and directly fetch the summary provided by the server. I’ll leave this problem for later when I have time to fix it.

Page Design#

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

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

This design indeed gave me a refreshing feeling amidst the flashy websites and apps, but the problem is that such an overly simple design is easily "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 headline 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 grid pages of a 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 a subscription path for readers willing to follow me, reminding them when I finally update.

At first, I thought this wouldn’t be difficult because Typecho itself provides an RSS feed. But then the problem arose: I placed the backend part (Typecho) under the domain blog.guhub.cn, while the front end is on www.guhub.cn, and Typecho itself is not designed for a front-end and back-end separation solution, so all article links in the subscription feed provided by Typecho point to the domain blog.guhub.cn, not the www.guhub.cn 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 imagined 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 fetched 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 subscription feed when needed. If you wish, you can subscribe to my blog using this link.

Others#

I won’t elaborate on the finer 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, and I shouldn’t feel bored enough to 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 should provide a much better reading experience than the current blog.

Oh, right, I almost forgot; I named this project Taco, which is a word derived from removing part of the phonetic element 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 spent less than a dollar in over half a month.

Website Ping Test Result

The nationwide green feels very comfortable.


These are probably the results of my recent blog tinkering.

Just a side note, if you’re observant, you’ll notice that the blog currently doesn’t have a friend link page; I’ll add that as soon as possible. I plan to reopen friend link applications and remove some links that are not frequently interacted with. I also have preliminary ideas for the future development of the blog. I might write a separate article about these topics, 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.