feature:show estimated time for reading a particular blog over the ca…#290
feature:show estimated time for reading a particular blog over the ca…#290devlopharsh wants to merge 8 commits intokeploy:mainfrom
Conversation
…rd#3578 Signed-off-by: developharsh <[email protected]>
|
hii @amaan-bhati @Achanandhi-M @gouravkrosx , please view this PR and acknowledge me accordingly , it will hardly take less than a minute please . Thank you !! |
amaan-bhati
left a comment
There was a problem hiding this comment.
Hey @devlopharsh Thanks for raising this pr and trying to add attention to detail in the blog posts. The idea to display the reading time is nice, but i think its placement can be improved. We can add the reading time right next to the name and the date posted, this would keep the information hierarchy entact, information kept at one single palce is a good practice we should try to focus on here. Hence, lets try to add the reading time right next to the author name and the date posted in the blog card.
|
@amaan-bhati |
Signed-off-by: developharsh <[email protected]>
…github.com/devlopharsh/keploy-blog-website into feat/show-estimated-blog-reading-time#3578
|
@amaan-bhati, I have shifted the time in the line of details as displayed in the Image. Hope that this is the one you are asking for !!
|
|
Hey @devlopharsh 👋 — thanks for putting this PR together, we appreciate the effort! We've gone ahead and requested a Copilot review on this. Here's some context from the reviewer:
Once you've had a chance to go through the comments, please address the feedback and resolve the threads — and we'll get this across the line. Feel free to ask if anything's unclear. Happy coding! 💙 |
There was a problem hiding this comment.
Pull request overview
Adds “estimated reading time” to blog post cards by computing word count from post content (HTML stripped) and displaying the result in list UIs.
Changes:
- Introduces a
calculateReadingTimeutility that strips HTML/entities before counting words. - Extends multiple WPGraphQL queries/fragments to include
contentso list views can compute reading time. - Updates
PostCardand its call sites to acceptcontentand render a “X min read” label.
Reviewed changes
Copilot reviewed 8 out of 9 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| utils/calculateReadingTime.ts | Adds HTML/entity stripping and TypeScript typing for reading-time calculation. |
| lib/api.ts | Fetches content in multiple list queries/fragments to support reading-time estimates. |
| components/post-card.tsx | Computes and renders reading time on each post card. |
| components/topBlogs.tsx | Passes content into PostCard for top blog sections. |
| components/TagsStories.tsx | Passes content into PostCard for tag listings. |
| components/postByAuthorMapping.tsx | Passes content into PostCard for author listings. |
| components/NotFoundPage.tsx | Passes content into PostCard for “latest posts” sections on 404. |
| components/more-stories.tsx | Passes content into PostCard for more-stories/search listings. |
| package-lock.json | Updates lockfile name metadata. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Function to calculate estimated time needed to read content | ||
| export function calculateReadingTime(content) { | ||
| export function calculateReadingTime(content: string) { | ||
| if (!content || typeof content !== "string") { |
There was a problem hiding this comment.
The function parameter is typed as string, but the implementation still defensively checks typeof content !== "string". Either widen the type (e.g., string | null | undefined / unknown) to match actual call sites, or drop the redundant runtime type check so the signature and behavior stay consistent.
| if (!content || typeof content !== "string") { | |
| if (!content) { |
| while (hasNextPage) { | ||
| const data = await fetchAPI( | ||
| ` | ||
| query AllPosts($after: String) { | ||
| posts(first: 50, after: $after, where: { orderby: { field: DATE, order: DESC } }) { | ||
| edges { | ||
| node { | ||
| title | ||
| excerpt | ||
| content | ||
| slug |
There was a problem hiding this comment.
getAllPosts() uses a while (hasNextPage) loop and reads data.posts.pageInfo, but the GraphQL query does not request pageInfo { hasNextPage endCursor }. As a result, pagination will never advance beyond the first page. Consider either adding pageInfo to the query or removing the loop and making the function explicitly fetch a single page.
| query AllPostsForSearch { | ||
| posts(first: 100, where: { orderby: { field: DATE, order: DESC } }) { | ||
| edges { | ||
| node { | ||
| title | ||
| excerpt | ||
| content | ||
| slug |
There was a problem hiding this comment.
Adding content to getAllPostsForSearch means the search page (and client-side cache in MoreStories) will download full HTML content for up to 100 posts, which can significantly increase build-time payload and client memory. Since the UI only needs a reading-time estimate, consider computing and returning a derived readingTimeMinutes (or wordCount) server-side instead of shipping full content to the browser.
| <PostCard | ||
| key={node.slug} | ||
| title={node.title} | ||
| coverImage={node.featuredImage} | ||
| date={node.date} | ||
| author={node.ppmaAuthorName} | ||
| slug={node.slug} | ||
| excerpt={getExcerpt(node.excerpt, 20)} | ||
| content={node.content} | ||
| isCommunity={true} | ||
| /> |
There was a problem hiding this comment.
communityPosts passed into TopBlogs comes from getAllPostsForCommunity (see pages/index.tsx), whose GraphQL query currently does not select content. That means node.content will be undefined here and reading time will fall back to the excerpt (less accurate than the PR’s stated goal). If full-content reading time is required, update the community posts query to include content as well.
components/post-card.tsx
Outdated
| <div className="flex items-center gap-1 mb-4"> | ||
| <span className="text-sm text-gray-500">{author ? author : "Anonymous"}</span> | ||
| <span className="text-gray-400">•</span> | ||
| <span className="text-sm text-gray-500"> | ||
| <Date dateString={date} /> | ||
| </span> | ||
| {readTimeLabel && ( | ||
| <> | ||
| <span className="text-gray-400">•</span> | ||
| <span className="text-sm text-gray-500">{readTimeLabel}</span> | ||
| </> |
There was a problem hiding this comment.
PR description mentions rendering the reading time as a top-right “pill”, but the implementation currently renders it inline in the author/date row. Either update the layout to match the described pill (e.g., absolutely positioned badge) or adjust the PR description to reflect the actual UI.
| const basePath = isCommunity ? "/community" : "/technology"; | ||
| const cleanedExcerpt = (excerpt || "").replace("Table of Contents", ""); | ||
| const readingTimeMinutes = calculateReadingTime(content || cleanedExcerpt || ""); | ||
| const readTimeLabel = | ||
| readingTimeMinutes > 0 ? `${Math.max(1, readingTimeMinutes)} min read` : null; |
There was a problem hiding this comment.
calculateReadingTime(...) runs several regex passes over the full post HTML on every render of PostCard. With content now fetched for list pages, this can become costly when rendering many cards (or when state updates trigger re-renders). Consider memoizing the computed value (e.g., based on content/excerpt) or precomputing reading time in the data layer.
Signed-off-by: harshdeveloper21 <[email protected]>

this PR fixed #3578
Related Tickets & Documents
Fixes: #3578
Description
issue - When browsing the blog, there’s currently no indication of how long a post might take to read. This makes it harder for readers to decide which article to open, especially when they are short on time.
solution - Introduce a consistent reading-time estimate for blog posts by calculating it from the full post content (with HTML stripped), and display this estimate prominently on each post card to improve content discoverability and user experience.
Changes
Type of Change
Testing
N/A no specific test needed .
Demo
Before :
After :
Environment and Dependencies
Checklist