Skip to content
A manual process to import existing exported disqus.xml content into an eleventy blog.
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Type Name Latest commit message Commit time
Failed to load latest commit information.


This is an intermediate step to migrate off of Disqus altogether but keep static copies of existing comment content.

Live demo (scroll down)

Read more at the Blog post


  • Way speedier than Disqus and no third party JS.
  • Comments available without JS.
  • Properly maintains threading.
  • Uses gravatar for avatar images.
  • Works with any existing links to Disqus comments in the wild (and there isn’t an annoying delay while the Disqus loads and jumps to the comment).
  • Full control over markup and style of comments (samples provided below).


You’ll need a new mechanism to add comments.

Get your Disqus XML

  1. Clone this this repository locally.
  2. Export your Disqus XML. Disqus Admin > Community > Export and click "Export".
  3. Save your Disqus XML file as input/disqus.xml (overwrite the sample file).

Map URLs to Local Template Files

The most annoying step of this process is creating your input/contentMap.json file (there is a sample file already in place). This maps each path of each Disqus comment thread to it’s eleventy template file.

Example disqus.xml thread url:

This script will normalizes to the URL path, extracting /web/23-minutes/ as the key (it will remove query parameters and de-dupe those threads). In my case, this url maps to ./_posts/ Each comment thread will need a map entry.

Open up input/contentMap.json to see an example.

You can use an eleventy template on your existing blog to generate this file for you! Try this contentMap.njk template:

permalink: contentMap.json
layout: false
{%- for post in collections.all %}
    "{{ post.url }}": "{{ post.inputPath }}",
{%- endfor -%}

You may have to modify the output a little bit but that’ll get you most of the way.

Run this script

# Install the dependencies
npm install

# Generate the comment JSON files
npm run default

This will create a bunch of files in output/.


Copy this file into your eleventy blog’s ./_data folder so that it’ll be available as the commentsCounts global.

I use commentsCounts to show comment counts on my home page big list of blog posts (on Here’s my liquid template for that:

{% for post in collections.posts %}
  {%- if commentsCounts[post.url] > 0 %}<span title="{{ commentsCounts[post.url] }} comment{% if commentsCounts[post.url] != 1 %}s{% endif %}">📢 {{ commentsCounts[post.url] }}</span>{% endif -%}
{% endfor %}

Individual template comments

The rest of the JSON files in output should have very similar names to your blog post entries. For example, will use 2017-11-21-23-minutes-font-loading.json. We want to copy these files to be in the same directory as our template files. In my case, this meant the _posts folder.


Eleventy will now make this JSON data available automatically in our markdown template (these are known as Template Specific Data Files).

Here’s how I used it (for maximum re-use, put it in a layout template—mine is _includes/layouts/post.liquid—otherwise you can use this on any template that might have one of our Disqus JSON comment files):

<div class="static-comments">
    <!-- `id` for direct link to Comments section -->
    <h2 id="comments">
        {{ disqus.commentCount }} Comment{% if disqus.commentCount != 1 %}s{% endif %}
    {% for comment in disqus.comments %}
        {% include comment-entry.html %}
    {% endfor %}

Note that each Disqus JSON comment file has a top level disqus key in the object. We are referencing that in the template.

And then comment-entry.html file in _includes/:

<div class="static-comments-reply" id="comment-{{ comment.postId }}">
    <div class="static-comments-hed">
        <img src="{{ comment.avatar }}" class="static-comments-img">
        <h3 class="static-comments-title">{{ }}</h3>
        <em class="static-comments-date"><a href="#comment-{{ comment.postId }}">{{ }}</a></em>
    <div class="static-comments-msg">{{ comment.message }}</div>
    {% for reply in comment.replies %}
        {% include comment-entry.html, comment: reply %}
    {% endfor %}

I using comment-postId as the linkable ID for each individual comment to reuse the same convention that Disqus uses (it’ll maintain existing links to comments).

For good measure, here’s the CSS I used:

.static-comments-reply {
    margin: 1em 0 3em;
.static-comments-reply > .static-comments-reply {
    margin-top: 2em;
    /* This is where the threading magic happens */
    padding-left: 1.5em;
    border-left: 4px solid #eee;
.static-comments-title {
    float: left;
    margin: 0;
.static-comments-img {
    float: left;
    max-width: 30px;
    margin-right: 1em;
    border-radius: 50%;
.static-comments-msg {
    clear: both;
    line-height: 1.7;
    margin-top: 1em;
.static-comments-date {
    float: left;
    clear: left;
    font-size: 0.8125em; /* 13px /16 */
@media (min-width: 25em) { /* 400px */
    .static-comments-date {
        float: right;
        clear: none;

/* Clearfixes */
.static-comments-msg:after {
    content: " ";
    display: table;
.static-comments-msg:after {
    clear: both;
You can’t perform that action at this time.