Backend API
While I’d prefer to skip this section since the backend APIs are fairly straightforward given our data model and required functionality, I’ll first walk through the list of APIs and then dive into the experience of building the frontend.
Given this data model for posts:
type Post struct {
ID string `json:"id,omitempty"`
Slug string `json:"slug,omitempty"`
Title string `json:"title,omitempty"`
Author string `json:"author,omitempty"`
Content string `json:"content,omitempty"`
Summary string `json:"summary,omitempty"`
Tags []string `json:"tags,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
}
The following APIs are provided by the backend service:
1. List Posts
- Method:
ListPosts
- Request:
ListPostsRequest
- Fields: None
- Response:
ListPostsResponse
- Fields:
Posts
(array ofPost
objects without Content field)
- Fields:
- Description: Retrieves a list of all blog posts.
2. Get Posts by Tag
- Method:
GetPostsByTag
- Request:
GetPostsByTagRequest
- Fields:
Tag
(string)
- Fields:
- Response:
GetPostsByTagResponse
- Fields:
Posts
(array ofPost
objects without Content field)
- Fields:
- Description: Fetches posts associated with a specific tag.
3. Get Tag Counts
- Method:
GetTagCounts
- Request:
GetTagCountsRequest
- Fields: None
- Response:
GetTagCountsResponse
- Fields:
TagCounts
(map withstring
keys andint64
values)
- Fields:
- Description: Returns the number of posts of each tag.
4. Get Post by ID
- Method:
GetPost
- Request:
GetPostRequest
- Fields:
PostID
(string)
- Fields:
- Response:
GetPostResponse
- Fields:
Post
(singlePost
object)
- Fields:
- Description: Retrieves a specific post by its unique
PostID
.
5. Get Post by Slug
- Method:
GetPostBySlug
- Request:
GetPostBySlugRequest
- Fields:
Slug
(string)
- Fields:
- Response:
GetPostBySlugResponse
- Fields:
Post
(singlePost
object)
- Fields:
- Description: Fetches a specific post by its URL-friendly
Slug
.
6. Create Post
- Method:
CreatePost
- Request:
CreatePostRequest
- Fields:
Post
(singlePost
object, ID and Slug fields are ignored)
- Fields:
- Response:
CreatePostResponse
- Fields:
PostID
(string),Slug
(string)
- Fields:
- Description: Creates a new blog post and returns its unique
PostID
andSlug
.
7. Update Post
- Method:
UpdatePost
- Request:
UpdatePostRequest
- Fields:
OriginalSlug
(string),Post
(updatedPost
object, ID and Slug fields are ignored)
- Fields:
- Response:
UpdatePostResponse
- Fields: None
- Description: Updates an existing post specified by
OriginalSlug
.
Given this user data mode:
type User struct {
Username string `json:"username,omitempty"`
HashedPassword []byte `json:"hashed_password,omitempty"`
Salt []byte `json:"salt,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
}
The following APIs are defined for user management:
1. Create User
- Method:
CreateUser
- Request:
CreateUserRequest
- Fields:
Username
(string): Unique username for the new user.Password
(string): Password for the new user account.
- Fields:
- Response:
CreateUserResponse
- Fields: None
- Description: Registers a new user with a username and password.
2. Authenticate User
- Method:
AuthenticateUser
- Request:
AuthenticateUserRequest
- Fields:
Username
(string): User's username.Password
(string): User's password.
- Fields:
- Response:
AuthenticateUserResponse
- Fields:
Token
(string): Authentication token that allows the user to access protected features.
- Fields:
- Description: Authenticates a user and returns a token for secure access.
Frontend
Before starting this project, my last experience with frontend development was five years ago, when I first learned React. Fortunately, React remains one of the most popular frontend libraries today, which helped make my learning curve a bit easier.
After deciding to use React, my next step was to choose a CSS framework. I didn’t want to spend too much time building CSS from scratch or dealing with conflicts between multiple frameworks. Ideally, I wanted to start with a template project that I could easily adjust to meet my design needs. And luckily, I came across this project - tailwind-nextjs-starter-blog.
Backend Integration
The original tailwind-nextjs-start-blog is a static website using React Server Component and Contentlayer to manage markdown content. To align it with my project, I needed to update it to integrate with my own backend APIs.
In React, components can be categorized as either server or client components based on where they render and how they interact with the user. Below is a comparison table highlighting the key differences between server and client components in React, particularly within frameworks like Next.js:
Feature | Server Components | Client Components |
---|---|---|
Rendering Location | Rendered on the server | Rendered on the client |
Interactivity | Non-interactive by default | Fully interactive |
Access to Browser APIs | No access (e.g., cannot use window , document ) | Full access (can use window , document , localStorage ) |
React Hooks | Cannot use hooks that rely on the client (e.g., useEffect ) | Can use all hooks, including useEffect , useState |
Performance | Faster initial load, as only HTML is sent to the client | May increase load time due to client-side JavaScript |
JavaScript Bundling | Does not contribute to client-side JavaScript bundle size | Adds to client-side JavaScript bundle size |
Data Fetching | Data can be fetched directly on the server, before render | Typically fetches data on the client, requiring loading states |
Use Cases | Static, non-interactive content like headers, footers, content-heavy pages | Dynamic and interactive content, such as forms, buttons, user-controlled states |
SEO | Optimized for SEO, as content is pre-rendered | Requires additional configuration for SEO |
Integration with Layouts | Typically used for layouts in Next.js, improving reusability and performance | Can be embedded within server layouts when interactivity is needed |
Directive | Default in Next.js for files without 'use client' | Must include 'use client' directive at the top of the file |
Hydration Requirement | No hydration needed, as it’s static HTML | Requires client-side hydration for interactivity |
For backend integration, the primary focus is on data fetching. As discussed in the first blog of this series, which covers the architecture, the backend APIs are only accessible to the web server and should not be publicly exposed. Therefore, any components calling backend APIs should be server components. An additional benefit is that server components are optimized for SEO.
Side Note: Another development advantage of using server components is their access to environment variables, since they are rendered on the server. This allows us to isolate the production environment from the local test environment, ensuring, for instance, that test environments don’t have access to production data and that test traffic isn’t recorded by production analytics plugins. With environment variables, we can control server component behavior and make changes by simply updating the variable—no rebuild necessary.
As for client components, here’s a workaround I used: let the server component parent read the environment variable, then pass its value as a prop to the client component.
Enhancements for a Complete Web Experience
With backend integration being done, the website can be launched with full functionality. But a few subtle features is worth mentioning, as they can enhance the overall user experience.
- Sitemap: A dynamically generated sitemap ensures search engines can easily crawl and index the site, improving discoverability.
- RSS Feed: For users who prefer to keep up with content through an RSS reader, an RSS feed is available. This allows readers to stay updated with the latest posts without needing to visit the site directly.
- Open Graph & Twitter Card: These metadata tags are essential for providing rich previews when sharing links on social media. By setting up Open Graph and Twitter Card tags, I ensure that links shared from the site display with attractive titles, descriptions, and images.
- robots.txt: This file communicates with search engines, indicating which pages or directories should be crawled or ignored. It’s a small detail but contributes to efficient crawling.
- Favicon: Adding a favicon ensures a recognizable icon appears in the browser tab, bookmarks, and other places where the site is saved. This small visual touch helps with brand identity.
- SEO Optimizations: Although the primary focus of the site isn’t SEO, thoughtful use of meta tags and keyword optimization improves visibility on search engines.
- Analytics Integration: Integrating analytics (such as Google Analytics) allows me to track user interactions on the site, helping me understand user behavior, popular content, and areas that may need improvement. This data is invaluable for optimizing the user experience over time.