# Embed comments from Discourse in your single page app

**URL:** https://meta.discourse.org/t/embed-comments-from-discourse-in-your-single-page-app/231798
**Category:** Administrators
**Tags:** embedding, how-to
**Created:** [July 3, 2022, 1:45pm UTC](https://meta.discourse.org/t/embed-comments-from-discourse-in-your-single-page-app/231798 "2022-07-03T13:45:37Z")
**Posts on this page:** 1
**Page:** 1

<div class="post-metadata">

### Author: ![MK2k](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/mk2k/32/49271_2.png) [@MK2k](https://meta.discourse.org/u/MK2k)
#### Post date: [July 3, 2022, 1:45pm UTC](https://meta.discourse.org/t/embed-comments-from-discourse-in-your-single-page-app/231798/1 "2022-07-03T13:45:38Z")

</div>

When trying to implement [[1]](https://meta.discourse.org/t/embedding-discourse-comments-via-javascript/31963) in your Single Page Application (SPA) which does not provide server side rendered pages, you will ultimately run into issues, see: [[2]](https://meta.discourse.org/t/discourse-comments-in-vuejs/106128)

So after a bit of tinkering, I’d like to present the following approach. The example is in Vue.js, however, it can be easily adapted to other frameworks/libs.

**Note** : I will use the term **blog posts** where a Discourse comments section should be embedded. But of course this can also mean individual pages on your site.

## 1. The issues in [[1]](https://meta.discourse.org/t/embedding-discourse-comments-via-javascript/31963)

### 1.1. `javascripts/embed.js` cannot work with client side rendered content

The `<script>...</script>` snippet you are told in [[1]](https://meta.discourse.org/t/embedding-discourse-comments-via-javascript/31963) to insert into your HTML will thus not be part of the implementation we are approaching here. We will utilize some bits of `javascripts/embed.js` provided by your Discourse instance as functions within our SPA.

### 1.2. Discourse cannot scrape client side rendered content

Discourse automatically creates topics for each blog post and tries to access the the original URL (of a blog post) to determine the title and content. This fails with an SPA, because Discourse will get the non-javascript part of it, e.g. `We're sorry but this site doesn't work properly without JavaScript enabled. Please enable it to continue.`

We will utilize the RSS Polling plugin to provide the necessary data and create the topics for us.

## 2. The implemenation

### 2.1 RSS Polling and the RSS/Atom feed

Create an endpoint on your site which provides an RSS or Atom feed for the RSS Polling plugin. This endpoint can either be just a **static XML formatted file** or a **server-side function** providing the XML formatted content, example:

**URL** : `https://mysite.com/blog.atom`

**Content** :

```plaintext
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

  <title>My Site Blog Posts</title>
  <link href="https://mysite.com/blog/"/>
  <updated>2022-07-03T09:02:48.721Z</updated>
  <id>urn:uuid:790c1857-b968-49cc-9fbd-bf7afe3552c2</id>

  <entry>
    <title>An Article about Technology</title>
    <author>
      <name>Your Name Here</name>
    </author>
    <link href="https://mysite.com/blog/an-article-about-technology"/>
    <id>urn:uuid:f6cc13e4-d2eb-4385-af28-c867a94f48dc</id>
    <published>2022-07-03T00:00:00Z</published>
    <updated>2022-07-03T00:00:00Z</updated>
    <summary>Let's discuss some technology in this article.</summary>
  </entry>

</feed>

```

Install the RSS Polling plugin for Discourse as per [[3] (Discourse hosted)](https://meta.discourse.org/t/configure-the-discourse-rss-polling-plugin/156387) or [[4] (self hosted)](https://meta.discourse.org/t/install-plugins-in-discourse/19157)

Recommended Settings for RSS Polling in Admin → Settings → Plugins:

| Key | Value |
| --- | --- |
| rss polling enabled | true |
| rss polling frequency | 10 (i.e. 10 minutes) |

Add a new feed in the RSS Polling plugin configuration at Admin → Plugins → RSS Polling

Configure it as per [[3]](https://meta.discourse.org/t/configure-the-discourse-rss-polling-plugin/156387):

| Key | Value |
| --- | --- |
| **URL** | `https://mysite.com/blog.atom` |
| **Category Filter** | `<this is optional>` |
| **Author** | `<define an author of the automatically generated topics>` |
| **Category** | `<define the category/ies where the automatically generated topics will be posted>` |
| **Tags** | `<this is optional>` |

### 2.2 SPA Router Configuration

Discourse uses the last part of the URL path as the identifier for an individual blog post.

Examples:

`https://mysite.com/blog/an-article-about-technology`  
`https://mysite.com/blog/another-article-about-cats`

Configure your SPA router accordingly, so that individual blog posts correspond with individual URLs.

Also: Discourse will provide a link back to the individual blog post, so it is good UX that when clicked, your site shows the actual article.

## 2.3 The Article Component

As told before, we can’t use the approach with the `<script></script>` from [[1]](https://meta.discourse.org/t/embedding-discourse-comments-via-javascript/31963). So we implement the iframe and some functions from `javascripts/embed.js` in our component:

**Article.vue**

Things you should edit:

| item | description |
| --- | --- |
| #YOUR-DISCOURSE-URL# | the URL of your Discourse instance, e.g. `discourse.mysite.com`) |
| #YOUR-SITE-URL# | the URL of your site, e.g. `mysite.com` possibly also `%2Fblog%2F` if your blog posts’ path is not /blog/ |

```js
<template>
  <div id="article">

    <!-- your formatted article here -->

    <iframe
      v-if="slug"
      v-bind:src="`https://#YOUR-DISCOURSE-URL#/embed/comments?embed_url=https%3A%2F%2F#YOUR-SITE-URL#%2Fblog%2F${slug}%2F`"
      id="discourse-embed-frame"
      width="100%"
      v-bind:height="`${iframeHeight}px`"
      frameborder="0"
      scrolling="no"
      referrerpolicy="no-referrer-when-downgrade"
    />
  </div>
</template>

<script>
export default {
  data: () => ({
    slug: null, // the slug of the blog post, e.g. "an-article-about-technology" while the route is "https://mysite.com/blog/an-article-about-technology"
    iframeHeight: 0 // Discourse will tell us the exact iframe height (see: receiveMessage method)
  }),

  methods: {
    // iframe communcation
    receiveMessage(event) {
      if (!event) {
        return;
      }
      if (!(event.origin || "").includes("#YOUR-DISCOURSE-URL#")) {
        return;
      }

      if (event.data) {
        if (event.data.type === "discourse-resize" && event.data.height) {
          this.iframeHeight = +event.data.height;
        }

        if (event.data.type === "discourse-scroll" && event.data.top) {
          // find iframe offset
          const destY = this.findPosY(this.$refs["discourse-embed-frame"]) + event.data.top;
          window.scrollTo(0, destY);
        }
      }
    },

    // Thanks http://amendsoft-javascript.blogspot.ca/2010/04/find-x-and-y-coordinate-of-html-control.html
    findPosY(obj) {
      var top = 0;
      if (obj.offsetParent) {
        while (1) {
          top += obj.offsetTop;
          if (!obj.offsetParent) break;
          obj = obj.offsetParent;
        }
      } else if (obj.y) {
        top += obj.y;
      }
      return top;
    }
  },

  async created() {
    this.slug = this.$router.currentRoute.path.split("/")[2];
  },

  mounted() {
    window.addEventListener("message", this.receiveMessage);
  },
  beforeDestroy() {
    window.removeEventListener("message", this.receiveMessage);
  }
}

```

### 2.4 Discourse Embed Configuration

Now with the RSS/Atom Feed polling in place and the implementation on your site, we can finally configure the embedding on the Discourse instance.

Go to Admin → Customize → Embedding and add a host:

| Key | Value |
| --- | --- |
| Allowed Hosts | your site base URL\> e.g. “[mysite.com](http://mysite.com)” |
| Class Name | optional class name for styling |
| Path Allowlist | e.g. “/blog/.\*” |
| Post to Category | same category as configured in RSS Polling Category |

## 3. Final Remarks

The RSS/Atom feed polling as well as Discourse itself will create a new topic if it does not exist for the individual blog post. Ensure that the RSS/Atom feed polling comes first (i.e. wait until the topic is created before you visit the blog post on your site).

Reason: Discourse can’t scrape the title and summary, so the topic would be `mysite.com` with the summary being `We are sorry, but this site does not work without javascript.` 😉

If for some reason Discourse came first, you can just delete the topic and wait until the RSS/Atom feed kicked in.

cheers

– MK2k
