Stream is a confusing term and it is used in multiple situations. The stream I discuss in this post refers a stream of events which is usually implemented by Kafka where the order of events matters. in . Such implementation can be found in Event Sourcing pattern to de-couple services. In some simpler cases, a stream can be used to get updates of remote resources asynchronously instead of consuming remote endpoints synchronously to improve the resiliency.
Normally such patterns come with a cost. By consuming the stream, we no longer have the control of what and how we want to consume. Therefore, we need to handle errors in such a way that other events are not impact. In other words, we need a more complicated error handling mechanism than just retrying to call remote endpoints. In this post, I would like to introduce few approaches that I find effective and useful.
Errors can come from many cases, it could come from bad codes or some dependency errors like DB or networks. The easiest approach is to skip those errors if possible. However, most of the time, we need to handle the error gracefully.
TLDR; I'm happy with Gatsby. However NextJS gives me more control as well as more configurations to optimize the blog. The post here describes how I created a blog via NextJS which helped me to improve the speed of the site from 1.8s FCP to 1.6s FCP.
Building a blog, I want it to be performant and blazing fast, and pre-rendered sites seems to be the best option out there. Pre-rendered sites allow browsers to render as early as possible. They then are hydrated to become SPA in order to inherit the smoother navigation any other cool stuff of SPA. Beside that, SEO is much more optimized with pre-rendered HTML sites.
JAMStack is popular due to those benefits. And in the JAMStack + React world, NextJS and GatsbyJS are the best options to pre-render or generate static sites. I chose NextJS for the reason that it allows me to customize for optimization and to learn. As a result, I was able to reduce the FP, FCP from 1.8s using Gatsby to 1.6s using NextJS with the same content & functionalities.
JAMStack doesn't specify what technology it includes. I, therefore, find it better to share some context about pre-rendering and the stack that will be used before we continue.
Simply, pre-rendering is a mechanism where the site is rendered in advance which is different from SPA where browsers evaluate Javascript codes to build the web site at client site. In pre-rendered sites, browsers only need to hydrate it and turn it into a SPA; hence the initial loading time is faster. There are two forms of pre-rendering:
Static Generation is the mechanism to generate sites in the form of HTML at build time and they will be served statically. Therefore, it can be easily scaled by CDN.
Server-side rendering is the mechanism to generate sites in the form of HTML on the server side for each request. Therefore, it allows for more personalized content.
NextJS supports both. However, we will use static generation for blogging as we don't need to customize content for each user request.
I've been writing via Markdown and I like it. Therefore, this blog will basically be started from a NextJS app with addition of rendering Markdown files.
I select the default starter in the prompt screen so I can understand NextJS more by adding features.
I will starting with routing, NextJS has an opinionated implementation of it. Files in /pages folder will be converted into a route. In the default starter, /pages/index.js and /pages/api/hello.js will be translated into two routes:
/ for /pages/index.js
/api/hello for /pages/api/hello.js
I don't need /api/hello so I will remove hello.js.
After that, I use yarn dev and head to localhost:3000 to make sure everything works. The website should be up and running at this point.
Beside the React component BlogPost, I provide 2 exported functions: getStaticProps and getStaticPaths.
getStaticProps provides props data for the page at build time. Thus, it allows the page to be rendered at build time. In the above example, getStaticProps provides title and html attributes from a markdown file so the page can be rendered.
getStaticPaths specify the list of routes to be generated at build time. It's only required for dynamic-routed pages. In the above example, getStaticPaths provides an array of slugs which refer to markdown files.
For the implementation of api.js, you can check out this file. It can be implemented by using remark and remark-html to parse the Markdown files.
getStaticPops here provides the props data for the build time which is the list of posts. I don't need to implement getStaticPaths for this file as the route is not dynamic.
Up until now, I already have basic functionalities of a blog. Next steps, we will discuss optimizations to improve the loading time.
The idea of images lazy loading is quite simple. We will put a placeholder in the image first and we only load the full image when it's visible on the screen. In order to generate the placeholder, I use a Webpack loader which is image-trace-loader. The loader will load an image as an object with two fields like:
import{ src, trace }from"./image.png";
In it, trace is a svg data and it will be used as the placeholder. src is the original source of the image. My webpack configuration will be like:
NextJS introduced granular chunking which helps to reduce the size of the initial bundle in most cases. However, in a simple blog, I find it more efficient to pack all common modules into one bundle instead of splitting them. Therefore, I just need to disable that option from NextJS. The change is quite simple:
{ experimental:{ granularChunks:false, }, }
I then use Lighthouse to examine the loading time. Without it, we do not know whether an optimization is effective or not. You can try multiple configurations for the best result.
NextJS is not just a simple SSR framework. Instead, it is actually a powerful framework by making things so easy to create a static website with getStaticProps. However, Hot Reload does not work well at the moment due to this issue. I hope that the development team will improve it in the near future.
One more thing, getStaticProps can be called multiple times for a same Markdown file and it can be optimized. I have done it by writing a Webpack loader to load those files to leverage Webpack caching mechanism. Also, Webpack loaders are much more reusable, it helps me save amount of working when playing with other frameworks like NuxtJS.
This is more like a note on how I debloated my Huawei P30 Pro than a technical post. It also worked for Mate 20 so I assume it works for Mate & P series and probably Honor series.
Before we start, please make sure that the device is connected to your laptop/computer. You can run the below command to verify it:
adb devices
It should list your device to the screen. To make the post short, installing ADB or connecting phones to computers would be out of topic here.
After connecting the phone to the laptop, I run this command to list all packages:
adb shell 'pm list packages -f' | sed -e 's/.*=//' | sort
Reading through that together with this guide, I get some packages to disable. I choose to disable over to uninstall as it allows me to enable packages later if something goes wrong. Here are some commands to disable them:
You can enable back those packages by using App section in Settings.
A bit off topic, this is for those who also want to get rid of some icons like NFC. I enable NFC all the time, it is redundant to show the icon in the status bar. Luckily, there is a way to do that with Huawei devices:
adb shell settings put secure icon_blacklist nfc,eyes_protect
A dark theme is a cool feature to have for a website. It was designed to reduce the luminance emitted by device screens and to improve visual ergonomics by reducing eye strain. However, it is perhaps the most complicated part of building my blog. I, therefore, feel that it would be helpful to write down my experience when implementing the feature.
I have been viewing a couple of blogs with dark theme support. The feature can be easily noticed by a switch to turn the dark theme on or off. Some of them even enable the dark theme as soon as I visit the site based on my machine setting. They are also able to store my preference so the website can render the proper theme when I revisit it. Given that, I decided to implement the feature for my blog. It should:
Have a switch to turn the dark theme on or off.
Store user preferences so the blog can show properly next time when the user visits.
That sounds simple, right? No, it's not at least for me.
This is the easiest part so I started with it first. Thanks to react-feather, I have two cool icons:
and . Next, I create a wrapper of them to reuse it in both the desktop navigation menu and the mobile navigation menu:
This simple component uses the function useState which is a React Hook to change the icon back and forth when a user clicks. For the functionality to change the color theme, we will add later.
My website uses TailwindCSS which has no support for dark themes out of the box. Therefore, I need to create a CSS file to have different color schemes for light and dark variants. The vars.css would look like:
It has a light color scheme by default and a dark color scheme with the attribute lights-out. It means we only need to toggle the HTML attribute to switch from light theme and dark theme. To present the idea in codes, only one line of codes in the function DarkModeSwitcher is needed. It is like below:
For those who may wonder, I use the default color palette from TailwindCSS. Also, I use light gray and dark gray instead of white and black to reduce eye strain following this guideline.
For storage solution, I use localStorage because of its simplicity. Then there are two problem left which are storing to localStorage and loading from localStorage.
Loading is trickier. Initially, I thought we only need these lines in the component constructor:
constDarkModeSwitcher:FC=()=>{ const storedDarkMode =window.localStorage.getItem(LIGHTS_OUT)==="true";// new codes const[darkMode, setDarkMode]=React.useState(storedDarkMode); // other codes };
Then I realized that gatsby build would fail because window is not available when Gatsby render sites on the server side aka SSR. We could move client-only codes to componentDidMount by using the useEffect hook. However, it would lead to a flickering issue for those we use dark theme because the site is loaded with light theme initially and it changes to dark right after being rendered.
React Context then came into the picture. It allows us to have client-only codes in gatsby-browser.js and sends the data deep down to our DarkModeSwitcher. In detail, I will start with a new Context object to store whether it's in dark mode or not. I add src/context/theme-mode.tsx like:
The Logic is very similar to our DarkModeSwitcher component. However, it introduces getInitialDarkMode to load information from localStorage and it uses that value as the initial state.
As we can get dark mode information from React Context, DarkModeSwitcher becomes simpler:
In order for DarkModeSwitcher to load the data from ThemeContext, I need to wrap the application inside the Context Provider which means adding below lines to gatsby-browser.js:
exportconstwrapRootElement=({ element })=>( <ThemeProvider>{element}</ThemeProvider> ); wrapRootElement.propTypes={ element:PropTypes.node.isRequired, };
To wrap it up, the website at this stage should have a button to switch from dark theme to light and vice versa. It is also able to store and load user preferences to and from localStorage for next visits.
Before going into the issue, here is how I tested the Gatsby site before production:
yarn build && yarn serve
Basically, it builds the site with production options and serves it under the default port 9000. Heading to http://localhost:9000, testing around, I found that the site flicker from the light theme to the dark theme on the first load. The reason is that the site was built without user's preferences and its default theme is the light theme. After loading from localStorage, it changes to the dark theme; hence, we see the site changes from the light theme to the dark theme very quickly.
My fix would require some knowledge of Gatsby. The idea is to add a piece of script on top of pre-rendered HTML codes so it is guaranteed to run before rendering the site. The script loads data from localStorage and updates the HTML attribute to ensure the site is rendered with the proper theme. In order to achieve this, I use the API onRenderBody in gatsby-ssr.js. The codes would look like:
Until now, the solution is complete. There is a switcher in order to turn the dark mode on and off. And the site is loaded with saved preferences in local storage for next visit. And most importantly, there is no flickering between the light theme and the dark them when users open the site.
The above solution works perfectly; however, I still find it a bit complicated and requires some knowledge on React & Gatsby. It motivated me to look for a simpler approach which is called Reactive CSS. Per my understanding, it means CSS is reactive so instead of maintain a React state, we use CSS to render the switcher:
As a result, Moon icon or Sun icon will be displayed based on whether there is a lights-out attribute in the root html or not. We no longer need ThemeContext nor the api wrapRootElement in gatsby-browser.js. Moreover, DarkModeSwitcher no longer requires an internal state, and it instead loads and updates the html attribute directly:
To summarize, two solutions were described in this post:
One solution with complicated techniques in React, Gatsby including: useEffect hooks, ThemeContext or wrapRootElement api in gatsby-browser.js.
Another simpler solution with mostly CSS.
Both require to inject a piece of scripts before the body to select the proper theme for users. And if you want extend it to select the dark mode based on the user's preferred color scheme, you can add these lines:
For myself, I learned a lot while implementing the dark theme. I eventually realize that I wouldn't need that knowledge because there is a simpler approach that requires CSS mostly. Such things happen normally, right? We often over-engineering a problem and it's why I believe we should always look for the best solution possible.
I would like to give credit to these posts that helped me to build my dark theme:
Coming from Golang background, writing codes without type is somehow uncomfortable. Therefore, I was looking for TypeScript support in Gatsby to accommodate that and gatsby-plugin-typescript is one of them. However, without core integration, the plugin has a couple of limitations like this issue. Fortunately, Gatsby is recently developing native support for Typescript (link) to allow us to enable TypeScript easier without a plugin.
You may notice that page-2.tsx is created with the default stater which means TypeScript is natively supported by Gatsby. However, there is still no type checking yet. We will implement it by installing these packages:
To verify it, I run tsc --noEmit. Sadly, I got this error and the reason is that the gatsby module is not resolved properly:
~/projects/gatsby-site % tsc --noEmit src/pages/page-2.tsx:3:33 - error TS2307: Cannot find module 'gatsby'. 3 import { PageProps, Link } from "gatsby" ~~~~~~~~ src/pages/page-2.tsx:5:20 - error TS7016: Could not find a declaration file for module '../components/layout'. '/Users/van.bong/projects/gatsby-site/src/components/layout.js' implicitly has an 'any' type. 5 import Layout from "../components/layout" ~~~~~~~~~~~~~~~~~~~~~~ src/pages/page-2.tsx:6:17 - error TS7016: Could not find a declaration file for module '../components/seo'. '/Users/van.bong/projects/gatsby-site/src/components/seo.js' implicitly has an 'any' type. 6 import SEO from "../components/seo" ~~~~~~~~~~~~~~~~~~~ Found 3 errors.
Checking node_modules/gatsby, it does define types in index.d.ts so the configuration is missing something. Actually, the fix is quite simple, I just add "moduleResolution": "node", to complierOptions in tsconfig.json.
Now, run tsc --noEmit again, gatsby module is resolved but there are some errors with importing layout.js and seo.js. It is totally fine, we will re-write those files in TypeScript.
For convenience, I added a script in package.json like "type-check": "tsc --noEmit", to have yarn type-check command.
Type checking will fail because some components are not declared with types. We can fix it by simply rewriting those files in TypeScript, defining props and declaring types. Let's take seo.js as an example. I first rename the file to seo.tsx for the sake of convention, then adding types for props:
We have added type checking for the Gatsby default starter without any additional plugins thanks to the recent TypeScript support from Gatsby team. For your reference, all the codes are pushed to https://github.com/bongnv/gatsby-typescript-starter.
Furthermore, due to the strong integration with TypeScript, VSCode should work nicely with the project including type checking as well as intellisense.
If your project happens to use eslint, you can add these packages for TypeScript support:
I have been writing a coupe of NPM packages recently and publishing those NPM packages was quite manual and repetitive. Including bumping versions, changelog and Github releases, the amount of manual work will cost plenty of time. It could easily take up to 30 minutes to have a nicely published NPM package. I then googled the problem and found sementic-release. The tool is very handy as it provides a bunch of cool features due to its plugins system.
semantic-release supports CI workflow as well as local workflow; however, I only used it with a local workflow.
In order to start using semantic-release, you will need to setup both NPM token as well as Github token in environment variables. I store them in one of my dotfiles for convenience. An example of my .localrc:
If you not sure how to generate those tokens, semantic-release-cli can help to generate them in few minutes and initialize configurations for a project. I used it in my first project to get tokens and to be familiar with the tool. I then no longer use it as it keeps asking passwords and MFA token which is inconvenient:
yarn global add semantic-release-cli semantic-release-cli setup
semantic-release-cli basically adds semantic-release as a development dependency and a couple of configs to package.json.
If GH_TOKEN and NPM_TOKEN are configured, it might be faster to add semantic-release manually:
npm install --save-dev semantic-release
I updated scripts in package.json for convenience:
{ "scripts":{ "release":"semantic-release" } }
So I only need yarn release to access its commands.
With the default configurations, semantic-release will analyze commits using Angular conventions to generate release notes. It then publishes to NPM repository and publishes a new release in Github. It also update package.json for the new package version but committing it. Therefore, an additional plugin is required in order to push the change to Github.
As I also want to generate CHANGELOG and commit the change to my repository automatically, I added two more plugins:
I didn't set up a CI as one-command release is more than enough for me. If you want to get of your hands free, you can try with Github Actions via this document. Another cool thing about semantic-release is that it can analyze the commit history to decide to change the version accordingly. Basically, everything can be automated.
I was using Gridsome to write this blog from Markdown contents. However, the transformer-remark plugin which transforms a Markdown content to a HTML content doesn't have the functionality to generate an excerpt automatically from the content itself. It requires the excerpt field to be filled manually in front matter. Plus I wanted it so bad that I couldn't wait for the PR to be merged and published to the npm repository. I then decided to create a patch for it so I can have the feature in my own blog as early as possible.
At first, I was thinking of publishing my own package which is a clone of transformer-remark plus the patch. The document is pretty straightforward and I actually used it. However I soon realized that:
It requires couple of steps to publish to npm.
We need to maintain the version of the package for new changes.
It would spam npm with unnecessary content if I keep doing it for every customization.
Later I found out that npm and yarn support to install a package from a git repository. It is so simple that you only need to:
Patch the change & push it to your own git repository (I would do the same thing for all of my codes).
Use the npm or yarn command like below to install it:
yarn add https://github.com/bongnv/transformer-remark // or npm install https://github.com/bongnv/transformer-remark
As the repository is a public one, any CI should have no problem to install the package from it. You won't need to register for a npm account nor to maintain a version of your package.
It's cool, right? I also happen to learn that beside git repository we can add from a local directory. We can also use yarn link or npm link to link a local fork of the package for testing purpose.
It turned out that vue-router couldn't handle the hash from the URL due to encoding issue and some weird behaviour in their scrolling logic. A more elegant fix is to override the scrollBehavior function in main.js like:
While I was adding a sidebar for my blog with GridSome, I realized that headings wasn't clickable. It's supposed to move the screen to the heading. I was wondering whether it's a wrong link, so I checked the source codes. However they were generated properly:
After googling, it looks like vue-router doesn't work well with scrollTo and sticky div element (link). And I use sticky for my sidebar. Luckily there is a comment to hack around the issue. A guy suggested to add these lines to use scrollIntoView instead of scrollTo:
The above solution works for most cases except when you open the site via the link with heading anchor for the first time. In order to be able scroll to heading I added:
mounted(){ this.$nextTick(()=>{ this.initObserver(); const id =this.$route.hash.slice(1); if(id.length!==0){ const el =document.getElementById(id); if(el){ el.scrollIntoView(); } } }); },
You may wonder why that I uses decodeURI to get the decoded heading anchor. By vue-router's implementation, hash param in the url is encoded for URI safe. Therefore, it has to be decoded to allow getElementById find the element properly.
TLDR; This article guides you not only how to create your blog but also how to continuously deploy with Github Actions. Even you are completely new to static site generators, it is very easy to get started with.
When it comes to blogging, there are millions of ways to create one. So why did I choose VuePress?
Frankly, there was no particular reason for me to start with VuePress over other static site generators like Gatsby or Hugo. It's mainly because of that I was learning VueJS. After using it, I would say VuePress is worth trying. Like VueJS, its tooling is friendly and the documentation is awesome.
VuePress uses Markdown for preparing content. For example, README.md will automatically be converted into index.html. I'll add these few lines to README.md as an example for the home page:
--- title: About --- <h1> Hi world! </h1> This is my blog.
The section between --- at the beginning of the Markdown file is called Frontmatter. There are other parameters like date or tags.
The default theme looks pretty and it works well for me. I'm going to use it for this blog which is @vuepress/blog. Applying a theme is as simple as one line change in config.js:
module.exports={ ... theme:"@vuepress/blog"// @vuepress/theme-blog also works }
No one would stop you from having your own structure; however you will need to experiment it on your own. I'm starting the very first entry for this blog:
--- title: The first entry date: 2020-04-29 --- This is my first blog entry.
VuePress then will generate the post in localhost:8080/post/2020/04/29/hello-world.html and the index page in localhost:8080/.
Ops, my home page is overriden!
No problem, we can move the index page to a different path (/posts/) by adding these lines to config.js:
module.exports={ ... themeConfig:{ directories:[ { // Unique ID of current classification id:"post", // Target directory dirname:"_posts", // Path of the `entry page` (or `list page`) path:"/posts/", } ], nav:[ { text:"Blog", link:"/posts/", }, ], } }
directories allows to configure document classifiers. So you can add writing or photography there.
nav is for the navigation bar. I prefer simplicity so Blog is enough for me.
Thanks to Github, CD (aka continuous delivery) is now a no-brainer task. Everything you need is just to create .github/workflows/deploy.yml with these lines:
In my case, the blog URL is https://bongnv.github.io/my-blog. However, it shows 404 because the base path by default is root (/). Adding this line to config.js will make it work:
module.exports={ ... base:"/my-blog/" }
Your local blog will become localhost:8080/my-blog instead.
Those are simplified steps to blog with VuePress, it should give you a simple blog to start writing. There are many things to explore deeper like Vue components inside Markdown, the plugins system. You could check out:
Today, I have just found this way to share my html document in the fastest way.
Honestly, it's pretty fun to serve a http static server in your current directory by only one line of code. Here is what I used: