Back to blog

How to Display Comment Count on Your Web Page: A Simple API Implementation Guide

Learn how to display comment count on a web page without slowing the page down: one API call, one cache, and a fallback for when the request fails. Includes the moderation-side view of what that number is actually counting. How to Display Comment Count on Your Web Page: A Simple API Implementation Guide is an EchoThread guide for site owners evaluating privacy-first comments, moderation, migration, performance, and reader engagement. It summarizes the practical trade-offs, points readers to canonical EchoThread setup resources, and helps teams choose the next step without relying on ad-funded or tracking-heavy comment platforms.

To learn how to display comment count on web page layouts reliably, you need to query an endpoint that returns approved records, cache the response, and inject the integer into your document. Fetching this data via an API gives your readers an honest signal of community discussion on post archives, topic cards, and article headers without forcing full comment threads to hydrate upfront.

Whether you run an engineering blog or manage an editorial team publishing dozens of stories daily, displaying comment counts requires balancing network budgets against fresh numbers. A broken counter makes an active discussion look dead or triggers layout shifts that degrade user experience. This guide walks through the exact mechanics: building a resilient client or server request, setting sensible cache lifetimes, avoiding common moderation traps, and keeping counters accurate across listing loops and article bylines.

What the comment count actually is (and what it is not)

Before writing integration code, define what that integer represents. A comment counter should reflect published, public reader contributions. That means:

  • Approved comments only: Unapproved submissions sitting in a moderation queue, rejected comments, and filtered spam must be excluded from public numbers. If an editor holds three comments for manual review, the public badge must not increment until those comments are explicitly approved.
  • Explicit handling of replies: Decide whether nested replies count toward the total or if your UI represents top-level threads. Most editorial layouts display the total sum of all approved comments and replies so readers understand the true depth of the conversation.
  • No ghost records: Soft-deleted comments (such as a placeholder stating "Comment removed by moderator") should only increment the count if editorial policy leaves the thread context intact. Hard-deleted entries must be dropped immediately.

Architecturally, platforms handle this in two distinct ways: a stored counter column on the post database record, or a dynamic tally derived at request time from the comment store. A stored post counter drifts over time whenever moderation actions happen out of band or database updates fail mid-transaction. Deriving the count from verified comment records keeps the number honest across your publication.

This count typically lives in three distinct template areas:

  1. Archive and category listing cards: Teaser cards where readers scan for active debates before clicking into an article.
  2. The article header: Adjacent to the byline, reading time, and publication date, providing context on engagement before the visitor reads the opening paragraph.
  3. The comment section header: Anchoring the discussion area at the bottom of the article to introduce the conversation thread.

Be careful when rendering client-side only. A badge injected purely via client-side JavaScript remains invisible to search engine robots and readers who disable scripts. As documented in Google Search Central's JavaScript SEO basics, crawlers can defer or miss dynamically rendered client elements when processing resources are constrained. For index pages where discussion volume helps search engines evaluate relevance, server-rendered numbers offer stronger discovery.

The Developer Spec: "The comment count display is an integer reflecting the total number of published, approved comments and replies tied to a specific thread ID. It excludes unreviewed submissions, rejected posts, and automated spam. It must be queryable via a GET request, cached at the edge, and fail silently by hiding the badge rather than outputting a false zero."

The API call: fetch comment count for one post or many

To display comment counts on custom CMS builds, you must execute an HTTP request against your discussion backend. At its simplest, your application sends a post identifier and receives an integer payload.

Here is a typical count response shape for an individual thread:

{
  "thread_id": "guide-how-to-display-comment-count",
  "count": 14,
  "top_level_count": 8,
  "reply_count": 6
}

When displaying comment counts across index listings with 20 or 50 articles, firing 50 individual requests creates a performance bottleneck. Browsers enforce limits on simultaneous HTTP connections to a single domain. Instead of dozens of roundtrips, execute a batched fetch comment count API call using comma-separated IDs or a query payload:

GET /api/v1/counts?threads=post-101,post-102,post-103 HTTP/1.1
Host: api.example.com
Accept: application/json

Response:

{
  "counts": {
    "post-101": 14,
    "post-102": 0,
    "post-103": 5
  }
}

Identifier Selection: Slugs vs. Thread IDs

Do not tie comment counts strictly to an editable URL slug. If an editor changes a headline from /news/draft-update to /news/final-announcement, any count tied purely to the slug resets to zero unless an alias migration runs. Use an immutable internal identifier (such as a database UUID or numeric post ID) as your discussion thread key, falling back to a normalized canonical slug only when primary keys are unavailable.

EchoThread publishes an API documented at its /docs/api page. In that architecture, thread identifiers map to clean read endpoints so templates can pull approved counts without leaking moderation metadata.

Minimal Fetch Implementation with Graceful Degradation

When querying your count endpoint via the browser, handle unexpected network failures gracefully. Following MDN Web Docs on Using the Fetch API, you must check response.ok before parsing JSON data, since HTTP error statuses like 404 or 500 do not reject the fetch promise outright.

<span class="comment-count-badge" data-thread-id="post-101" hidden>
  <span class="count-value"></span> comments
</span>

<script>
async function loadCommentCount(threadId) {
  const badge = document.querySelector(`.comment-count-badge[data-thread-id="${threadId}"]`);
  if (!badge) return;

  try {
    const res = await fetch(`https://api.example.com/v1/counts?thread=${encodeURIComponent(threadId)}`);
    if (!res.ok) {
      throw new Error(`HTTP error! status: ${res.status}`);
    }
    const data = await res.json();
    
    // Only display badge if count is valid
    if (typeof data.count === 'number') {
      badge.querySelector('.count-value').textContent = data.count;
      badge.removeAttribute('hidden');
    }
  } catch (err) {
    // Degrade quietly: do not display "0 comments" when the network fails
    badge.setAttribute('hidden', '');
  }
}

loadCommentCount('post-101');
</script>

Batch Fetching on Archive Pages

For index layouts where dozens of cards appear together, query all visible post identifiers in a single network trip. This script collects all data attributes from post cards, issues one batch request, and populates the badges dynamically:

<script>
async function populateBatchCounts() {
  const badges = document.querySelectorAll('[data-comment-thread-id]');
  if (!badges.length) return;

  const ids = Array.from(badges).map(el => el.getAttribute('data-comment-thread-id'));
  const uniqueIds = [...new Set(ids)];

  try {
    const res = await fetch(`https://api.example.com/v1/counts?threads=${encodeURIComponent(uniqueIds.join(','))}`);
    if (!res.ok) throw new Error(`HTTP error ${res.status}`);
    
    const data = await res.json();
    const counts = data.counts || {};

    badges.forEach(badge => {
      const id = badge.getAttribute('data-comment-thread-id');
      const count = counts[id];
      if (typeof count === 'number') {
        const label = badge.querySelector('.count-text') || badge;
        label.textContent = count === 1 ? '1 comment' : `${count} comments`;
        badge.removeAttribute('hidden');
      }
    });
  } catch (err) {
    // Leave badges hidden on failure to avoid layout thrashing
    console.warn('Unable to load discussion counts:', err);
  }
}

document.addEventListener('DOMContentLoaded', populateBatchCounts);
</script>

Server-Side Rendering (SSR) and Static Generation Patterns

While client-side fetching is straightforward to install, server-side integration ensures zero layout shift and makes engagement data visible to crawlers immediately upon response delivery.

If you build with modern static site generators (SSGs) or edge-rendered frameworks, fetch the count data during your page build or request middleware:

Build-Time Fetching (Static Generation)

When generating pages at build time, query your comment count API inside your data loader. Here is how that pattern looks in modern JavaScript environments:

export async function getStaticProps({ params }) {
  const post = await getPostBySlug(params.slug);
  
  let commentCount = 0;
  try {
    const res = await fetch(`https://api.example.com/v1/counts?thread=${post.id}`, {
      headers: { 'Authorization': `Bearer ${process.env.COMMENT_API_READ_TOKEN}` }
    });
    if (res.ok) {
      const data = await res.json();
      commentCount = data.count || 0;
    }
  } catch (err) {
    // Fall back to 0 or null during build pipeline
    commentCount = 0;
  }

  return {
    props: {
      post,
      commentCount
    },
    // Revalidate every 5 minutes (ISR)
    revalidate: 300
  };
}

This approach gives search bots a pre-rendered integer inside the initial HTML payload without running client JavaScript. For decoupled frontend setups, consult our guide on installing a comment system for headless CMS architectures to configure secure API tokens and caching layers.

Where the count goes in your templates

Integrating counts across your templates requires thoughtful placement across three distinct touchpoints:

1. Listing Loops and Archive Cards

In listing templates, pair the count with an icon or clear text label. Place the count inside your post meta row alongside the author and publication date. If a post has zero comments, decide on a consistent editorial rule: either display "0 comments" to invite the first contribution, or hide the counter entirely until the first approved comment arrives.

2. The Post Byline

Place the count alongside your article publication metadata. Ensure that clicking this badge takes the user directly to the discussion rather than refreshing the page. Use an anchor target pointing directly to your comment wrapper:

<a href="#comments" class="byline-comments" aria-label="Jump to 14 reader comments">
  <svg aria-hidden="true" width="16" height="16" viewBox="0 0 24 24">
    <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
  </svg>
  <span>14 Comments</span>
</a>

3. The Comment Container Header

Directly above your comment widget or custom stream, use an <h2> or <h3> carrying the explicit count. This confirms to the reader that they have reached the correct section and matches the number shown on the archive card. If your comment thread supports sorting (such as newest first or most upvoted), update this header count only when approved items change, not when the sort order toggles.

Preventing Cumulative Layout Shift (CLS)

If you load counts on the client side, empty containers that pop into view once the API responds will push adjacent content downward. This instability hurts your Core Web Vitals score.

To avoid layout shift:

  • Reserve spatial dimensions: Apply a fixed min-width and min-height to the badge container using CSS so the layout engine reserves space before the network request resolves.
  • Use invisible placeholders: Render a skeleton placeholder or an invisible numeric character (like a non-breaking space) inside the element until the fetch finishes.
  • Opacity transitions: Instead of switching between display: none and display: inline, render the container with opacity: 0 and transition to opacity: 1 once populated.

Accessibility Requirements

A bare number inside a styled <span> provides poor accessibility for visually impaired visitors. A screen reader encountering a standalone "14" will announce it out of context. Per MDN Web Docs on aria-label, you should supply explicit labels describing interactive elements. Use an aria-label="14 comments on this article" or nest visually hidden text using standard utility classes:

<span class="comment-count">
  <span aria-hidden="true">14</span>
  <span class="sr-only">comments posted</span>
</span>

Operational realities: moderation queues, cache invalidation, and edge sync

Comment counts are read-heavy and write-light. On high-volume publications, querying a live database or remote service on every single post-card render across thousands of simultaneous visitors will quickly trigger rate limits or exhaust server connections.

Under the RFC 9111 HTTP Caching specification, origin servers and intermediate caches use freshness lifetimes and revalidation directives to serve traffic efficiently. You should configure count responses with explicit cache lifetimes. A practical caching strategy uses Cache-Control: public, max-age=60, stale-while-revalidate=300. This configuration instructs CDNs and browsers to treat the count as fresh for 60 seconds, while allowing stale values to serve instantly for up to five minutes while background revalidation fetches updated figures.

If your application floods an upstream API with unbatched requests, servers may issue an HTTP 429 response. As described in MDN Web Docs for HTTP 429 Too Many Requests, applications encountering rate limiting must read the Retry-After response header and pause subsequent requests using exponential backoff.

The Moderation Sync Problem

For community managers and editors responsible for active discussions, comment counters can become a source of reader friction when moderation queues are backlogged. Consider the operational flow:

  1. A reader posts a comment on an article with pre-moderation enabled.
  2. The submission enters the moderation queue awaiting review.
  3. The reader refreshes the article. If the counter increments to show their submission while the comment itself remains hidden, the reader assumes the thread is broken.
  4. Conversely, if an aggressive spam attack drops 50 commercial links into a thread, public counters must not broadcast 50 comments before automated filters or moderators review the queue.

To keep the numbers truthful, your count API must query strictly approved records. When building moderation pipelines or configuring discussion backends, link your counter directly to the published state. EchoThread keeps moderation operations streamlined across teams; on EchoThread Starter and above, moderators receive alerts and email links to approve or reject submissions without manual database surgery, while deterministic rules like restricted-words lists run before spam evaluation. You can review how structured workflows manage queue throughput in our guide on comment moderation practices, or consult the EchoThread documentation for platform integration details.

Handling edge failures and network anomalies

When an API endpoint times out or returns an error, your template code must fail gracefully. Consider this matrix of edge scenarios and practical engineering resolutions:

Failure Scenario Direct User Impact Recommended Engineering Resolution
API times out (504 Gateway Timeout) Page load freezes or displays broken text strings Set a 2-second timeout on client fetch; keep badge hidden with hidden attribute so readers see no broken elements.
Thread ID not found (404 Not Found) Badge shows "NaN" or "undefined" Parse JSON safely; treat missing thread IDs as count 0 or suppress badge display entirely.
Upstream rate limit (429 Too Many Requests) Client consoles flood with errors; counters disappear Batch multiple thread queries into single GET requests; serve stale cached values via stale-while-revalidate edge directives.
Headline edit changes URL slug Comment count abruptly resets to zero Decouple thread lookup from mutable URL paths; bind threads to persistent numeric post IDs or content UUIDs.
Spam wave hits moderation queue Counter inflates with spam before manual review Ensure API endpoints query only status = 'approved' records, completely excluding pending and rejected submissions.

Pricing and infrastructure budgeting for discussion systems

When choosing how to power your site's commenting and count infrastructure, evaluate the operational costs of hosted APIs versus building custom database tables. Maintaining custom high-concurrency counter pipelines requires cache invalidation workers, database read replicas, and spam mitigation tooling.

Hosted solutions provide ready-made count endpoints alongside turnkey moderation dashboards. EchoThread is a hosted SaaS commenting platform that installs as a single script tag with vanilla JavaScript and zero dependencies; EchoThread does not offer a self-hosted or on-premise deployment. Comments are unlimited on every EchoThread plan, including the free EchoThread Hobby plan. For publications planning their operational budget, review plan details on the EchoThread pricing page: EchoThread Hobby is free for 1 site; EchoThread Starter is $5 a month or $50 a year for 3 sites; EchoThread Pro is $19 a month or $190 a year for 10 sites; and EchoThread Business is $79 a month or $790 a year for unlimited sites. EchoThread yearly billing costs ten months' price for twelve months. EchoThread sites created on or after 1 October 2026 carry a soft monthly page-view allowance (Hobby 10,000, Starter 100,000, Pro 1,000,000, Business unlimited), and EchoThread emails the owner at 90% and at the allowance and hides or blocks nothing.

Frequently Asked Questions

Should comment counts update in real time or via cached intervals?

For almost all editorial and media sites, cached intervals of 60 seconds to five minutes are far superior to real-time WebSockets. Comment totals are read by thousands of casual visitors but only change when a new contribution is approved. Serving cached numbers via a CDN edge cache with stale-while-revalidate directives reduces database load by orders of magnitude while keeping counts sufficiently fresh for human readers.

How should unapproved or pending comments affect the displayed count?

Unapproved and pending comments should not increment the public comment count. Displaying a count that includes unreviewed comments creates a confusing experience where the badge reads "3 comments" but the discussion area displays zero items. Restrict count calculations strictly to approved, published records.

Why do comment counts sometimes reset when changing URL slugs?

This issue occurs when the discussion backend uses the raw page URL as the thread identifier. If an author updates a slug to improve SEO or fix a typo, the discussion platform treats the new URL as an entirely fresh, empty thread. To prevent this, often bind comment threads to an immutable internal ID, such as a CMS article ID or database UUID.

How can you display comment counts without causing Cumulative Layout Shift (CLS)?

To eliminate CLS when fetching counts on the client side, reserve space in your CSS using a defined min-width and min-height on the badge wrapper. Alternatively, render an invisible placeholder character or hide the element cleanly until the JSON response resolves, ensuring that text does not jump when the integer populates.

Can search engines index dynamically injected comment counts?

Search crawlers can sometimes execute JavaScript and discover dynamically injected counts, but client-side rendering varies across different bots and indexers. Crawlers operating under strict compute or bandwidth budgets may index the page before client API requests resolve. For intended indexing of comment counts on archive and article pages, render the count server-side during the initial HTML response or build step.

Discussion

Comments

This thread runs on EchoThread — the same widget you would add to your own site.

No comments yet.

Ready to try EchoThread?

Free for your first site. Set up in under a minute.

Create free account