I have had a personal blog for a while now. I had written a few posts there but it was far from perfect. It was built using basic HTML, CSS and JS. I had seen all this awesome sites with unique designs and I thought, why not create my own?
I went with a front-end for back-end approach which means the back-end needed to be robust in order for the content to load properly and fast.
I built my API using FastAPI for Python and the webapp using VueJS.
Building the API
Structering the API
I laid down the endpoints that will possibly be needed in order for the blog to work properly.
Here are some of them
- /posts: Allow GET, POST, UPDATE, DELETE where only GET will be public and other methods would need a secret token in order to access them
- /subscribe: Allow POST, DELETE where only POST will be public.
- /related: Allow GET to get the related posts to a post.
For the database, I went with mongoDB. So the idea is to store the posts as markdown in the database and let the API access it. The frontend will then just make a request and get all the data from the API.
Implementing the API
It took me a few days to get the API ready. FastAPI was really helpful with their openapi docs to provide a nice interface in order to test the API without using curl
.
The posts and the subscribe endpoint are pretty self explanatory, here's how I created the related endpoint.
Finding posts related to a post
Since all the posts will have tags linked to them, I used those to calculate a score for the post.
The /related/<post_id>
endpoint was structered to pass a post_id
that would tell us which post to consider the root post. Once we have this post, we can fetch all the other posts and calculate a related score.
This score is calculated in the following way
# Consider root_tags are tags of the source post
# other_tags are the tags of the other post that.
def calculate(root_tags: List, other_tags: List) -> int:
# Remove duplicate tags if present
root_tags = set(root_tags)
other_tags = set(other_tags)
# Calculate the score now
score = len(root_tags.intersection(other_tags)) / len(root_tags)
return score
Above code does the following:
- Intersect the root tags with the other tags set giving us the common tags
- Score is the division of the number of tags common between the two posts and the total number of tags present in the actual post.
This way we get a score that would be between 0 and 1. Once we have this score, we can sort the posts based on the result and the posts that have a higher score are more related to a post as compared to other posts.
Building the webapp
The webapp is built using VueJS. The whole idea of the frontend for backend approach is, the frontend will be dependent on the backend for the data.
Structuering the app
Before building the app, I went through a few points that the app should be able to do
- It should have a home page that will show the users all the posts available
- It should be able to show each post
- It should be fast
- It should be able to load posts directly through a route (for example:
blog.com/nana
should load the postnana
and not just the webapp that is hosted onblog.com
)
The first part is pretty simple. I just used the /posts
route in order to get all the posts and then displayed them in a nice way.
Rendering the post dynamically
Here's the flow of how a post is rendered
- If the user clicks on a post from the home page, the content of the post is passed to the router and accordingly rendered in the post view.
- If the post is opened using a route, the route is used to find the post and accordingly the content is shown.
The above basically does two things:
- Makes the load speed faster if the user opens a post from the home page
- Adds the ability to load a post using the route.
To pass the contents of the post by the route, use a route prop to pass a object that will hold the contents. In the Post view, check if this object is available or not, if not use the route to make a request and fetch the content.
What about SEO?
Well yeah, I know SEO is important. For loading the meta tags I used vue-head which renders the meta tags dynamically after the post is loaded using the API.
This is pretty important since the meta tags are used by all the bots crawling the page. Also, Google bots are now able to crawl dynamically rendered content whcih means it should not be an issue if the tags are loaded dynamically using JS.
Problems
Except the common occurence of bugs, I did not had any problems with the back end. However there was one issue that made me question the whole idea. How do bots that do not have the ability to crawl dynamically rendered content crawl the page.
For example, twitter bots crawl a page in order to show a nice card view. If the bots are not able to crawl the page then the card won't be there. Not just Twitter, a similar functionality is used by various other social share bots like the ones from Facebook and LinkedIn.
How to let bots crawl the page
Well, so how did I fix this issue? At first, I obviously thought this would be inevitable because there's no way the bots would be able to detect dynamically rendered content. One solution was to go with server side rendering but I'm better off not diving down that road.
So the solution that I went with was to write a static file server in Python.
What would our server do?
- It should be able to return the static html, JS, CSS files.
- It should be able to return a rendered HTML with just the meta tags if the request is made by a bot.
I built the server using Flask for Python. It detects the request making entity using the User-Agent
header being passed and accordingly returns an HTML. If the request is being made by a bot, it returns some HTML content that has the meta representing the post.
Else it returns the proper static files.
You can read about it here
You can check my blog page here
This post is also published in my personal blog
Top comments (2)
Looks good my friend, thanks for posting this!
Thank you!