Skip to main content
๐Ÿ›ก๏ธ Verified Technical Content: Written by Serhii Hrekov. | Last reviewed & updated in Git: July 21, 2026

How to Display Blog and Documentation Counts in Docusaurus

ยท 6 min read
Serhii Hrekov
Senior Software Engineer & System Architect specializing in Python, Web Systems, Cloud Infrastructure & Automation

Adding a metric counter that displays the total number of blog articles and documentation pages is a great way to make a Docusaurus site feel active.

However, since Docusaurus compiles your pages statically, you cannot call internal hooks like useBlogPosts() inside static landing pages (such as src/pages/index.js). Doing so causes server-side rendering (SSR) failures during build execution on platforms like Vercel.

This guide provides a robust, build-time solution that counts flat files, subdirectories, and nested folders, saving the results to a static JSON file that can be safely imported anywhere in your project.


The Directory Counting Challengeโ€‹

A typical Docusaurus repository organizes content in two distinct formats:

  1. Flat Files: Individual markdown files sitting directly in the parent directory (e.g. blog/my-first-post.md).
  2. Folder-based Structures: Nested subdirectories that contain an entry index file (e.g. blog/nested-post/index.mdx).

A naive file counter that only checks file extensions in the root directory will skip folder-based articles entirely. To count all items correctly, the script must parse flat files, check subdirectories for markdown content, and recursively traverse documentation paths.


Implementing the Build-Time Scriptโ€‹

Create a Node.js script in your project root at scripts/blogStats.js. This script scans your blog/ and docs/ paths, calculates their sizes, and saves the output to a JSON file:

// scripts/blogStats.js
const fs = require('fs');
const path = require('path');

const blogDir = path.join(__dirname, '..', 'blog');
const docsDir = path.join(__dirname, '..', 'docs');

/**
* Counts blog articles.
* Checks for individual markdown files and folders containing a markdown file.
*/
function countBlogEntries(dir) {
if (!fs.existsSync(dir)) return 0;

let count = 0;
const entries = fs.readdirSync(dir, { withFileTypes: true });

for (const entry of entries) {
const fullPath = path.join(dir, entry.name);

if (entry.isFile() && (entry.name.endsWith('.md') || entry.name.endsWith('.mdx'))) {
count += 1;
} else if (entry.isDirectory()) {
const innerFiles = fs.readdirSync(fullPath);
const hasMarkdown = innerFiles.some(f => f.endsWith('.md') || f.endsWith('.mdx'));
if (hasMarkdown) {
count += 1;
}
}
}

return count;
}

/**
* Recursively counts all markdown files in the docs directory.
*/
function countMarkdownFilesRecursive(dir) {
if (!fs.existsSync(dir)) return 0;

let count = 0;
const entries = fs.readdirSync(dir, { withFileTypes: true });

for (const entry of entries) {
const fullPath = path.join(dir, entry.name);

if (entry.isFile() && (entry.name.endsWith('.md') || entry.name.endsWith('.mdx'))) {
count += 1;
} else if (entry.isDirectory()) {
count += countMarkdownFilesRecursive(fullPath);
}
}

return count;
}

const blogCount = countBlogEntries(blogDir);
const docsCount = countMarkdownFilesRecursive(docsDir);
const totalCount = blogCount + docsCount;

// Ensure output directory exists
const outputPath = path.join(__dirname, '..', 'src', 'data');
if (!fs.existsSync(outputPath)) {
fs.mkdirSync(outputPath, { recursive: true });
}

// Write counts to JSON
fs.writeFileSync(
path.join(outputPath, 'blogStats.json'),
JSON.stringify({
blog: blogCount,
docs: docsCount,
total: totalCount
}, null, 2)
);

console.log(`โœ… Blog count: ${blogCount}`);
console.log(`โœ… Docs count: ${docsCount}`);
console.log(`โœ… Total count: ${totalCount}`);

Hooking the Script into Docusaurus Build Lifecycleโ€‹

Configure your package.json to execute this counter before building the static site:

{
"scripts": {
"prebuild": "node scripts/blogStats.js",
"prestart": "node scripts/blogStats.js",
"build": "docusaurus build",
"start": "docusaurus start"
}
}

By leveraging prebuild and prestart, Node.js runs the count calculations automatically before Docusaurus compiles the source files.


Creating the Counter Componentโ€‹

Now, create a React component at src/components/BlogPostCount.js to import and display this statistics data:

// src/components/BlogPostCount.js
import React from 'react';
import stats from '../data/blogStats.json';

export default function BlogPostCount() {
return (
<div className="stats-container">
<p>๐Ÿ“š Total Blog Articles: <strong>{stats.blog}</strong></p>
<p>๐Ÿ“„ Documentation Pages: <strong>{stats.docs}</strong></p>
<p>โœจ Combined Total: <strong>{stats.total}</strong></p>
</div>
);
}

Import this component into your home page (src/pages/index.js) to display the live count:

// src/pages/index.js
import React from 'react';
import Layout from '@theme/Layout';
import BlogPostCount from '../components/BlogPostCount';

export default function Home() {
return (
<Layout title="Home">
<main className="container margin-vert--xl">
<h1>Welcome to My Site</h1>
<BlogPostCount />
</main>
</Layout>
);
}

Sourcesโ€‹