[1]을 서버 사이드 렌더링된 페이지를 제공하지 않는 싱글 페이지 애플리케이션(SPA)에 구현하려고 할 때 결국 문제가 발생하게 되며, 자세한 내용은 [2]를 참고하십시오.
조금 만져본 끝에, 다음과 같은 접근 방식을 제안하고자 합니다. 예제는 Vue.js로 작성되었지만, 다른 프레임워크/라이브러리에 쉽게 적용할 수 있습니다.
참고: Discourse 댓글 섹션이 임베드되어야 할 위치를 블로그 포스트라는 용어로 사용할 것입니다. 하지만 물론 이는 웹사이트 내 개별 페이지를 의미할 수도 있습니다.
1. [1]의 문제점
1.1. javascripts/embed.js는 클라이언트 사이드 렌더링된 콘텐츠와 함께 작동할 수 없습니다
[1]에서 HTML에 삽입하도록 안내받은 <script>...</script> 스니펫은 따라서 여기에서 다루는 구현 방식의 일부가 될 수 없습니다. 대신 Discourse 인스턴스가 제공하는 javascripts/embed.js의 일부 기능을 SPA 내에서 함수로 활용하겠습니다.
1.2. Discourse는 클라이언트 사이드 렌더링된 콘텐츠를 크롤링할 수 없습니다
Discourse는 각 블로그 포스트에 대해 자동으로 주제를 생성하고, 제목과 콘텐츠를 결정하기 위해 원래 URL(블로그 포스트의)에 접근을 시도합니다. SPA의 경우 이는 실패하게 되며, Discourse는 JavaScript가 없는 부분, 예: We're sorry but this site doesn't work properly without JavaScript enabled. Please enable it to continue.와 같은 내용을 받게 됩니다.
필요한 데이터를 제공하고 주제를 생성하기 위해 RSS 폴링 플러그인을 활용하겠습니다.
2. 구현
2.1 RSS 폴링 및 RSS/Atom 피드
RSS 폴링 플러그인을 위해 RSS 또는 Atom 피드를 제공하는 엔드포인트를 웹사이트에 생성하십시오. 이 엔드포인트는 정적 XML 형식 파일이거나 XML 형식 콘텐츠를 제공하는 서버 사이드 함수일 수 있으며, 예시:
URL: https://mysite.com/blog.atom
콘텐츠:
<?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>
[3] (Discourse 호스팅) 또는 [4] (셀프 호스팅)에 따라 Discourse용 RSS 폴링 플러그인을 설치하십시오.
관리자 → 설정 → 플러그인에서 RSS 폴링을 위한 권장 설정:
| 키 | 값 |
|---|---|
| rss polling enabled | true |
| rss polling frequency | 10 (즉, 10분) |
관리자 → 플러그인 → RSS Polling에서 RSS 폴링 플러그인 설정에 새 피드를 추가하십시오.
[3]에 따라 설정하십시오:
| 키 | 값 |
|---|---|
| URL | https://mysite.com/blog.atom |
| Category Filter | <선택 사항> |
| Author | <자동 생성된 주제의 저자를 정의> |
| Category | <자동 생성된 주제가 게시될 카테고리/들을 정의> |
| Tags | <선택 사항> |
2.2 SPA 라우터 구성
Discourse는 URL 경로의 마지막 부분을 개별 블로그 포스트의 식별자로 사용합니다.
예시:
https://mysite.com/blog/an-article-about-technology
https://mysite.com/blog/another-article-about-cats
개별 블로그 포스트가 개별 URL에 대응하도록 SPA 라우터를 적절히 구성하십시오.
또한: Discourse는 개별 블로그 포스트로 돌아가는 링크를 제공하므로, 클릭 시 웹사이트가 실제 기사를 표시하는 것이 좋은 UX입니다.
2.3 Article 컴포넌트
앞서 설명했듯이, [1]의 <script></script> 접근 방식을 사용할 수 없습니다. 따라서 iframe과 javascripts/embed.js의 일부 기능을 컴포넌트에 구현합니다:
Article.vue
수정해야 할 사항:
| 항목 | 설명 |
|---|---|
| #YOUR-DISCOURSE-URL# | Discourse 인스턴스의 URL, 예: discourse.mysite.com) |
| #YOUR-SITE-URL# | 웹사이트의 URL, 예: mysite.com, 블로그 포스트 경로가 /blog/가 아닌 경우 %2Fblog%2F도 포함될 수 있음 |
<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, // 블로그 포스트의 슬러그, 예: "an-article-about-technology" (라우트는 "https://mysite.com/blog/an-article-about-technology"일 때)
iframeHeight: 0 // Discourse가 정확한 iframe 높이를 알려줍니다 (참조: receiveMessage 메서드)
}),
methods: {
// iframe 통신
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) {
// iframe 오프셋 찾기
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 임베드 구성
이제 RSS/Atom 피드 폴링이 설정되고 웹사이트가 구현되었으므로, 마침내 Discourse 인스턴스에서 임베딩을 구성할 수 있습니다.
관리자 → 사용자 정의 → 임베딩으로 이동하여 호스트를 추가하십시오:
| 키 | 값 |
|---|---|
| Allowed Hosts | 웹사이트 기본 URL, 예: “mysite.com” |
| Class Name | 스타일링을 위한 선택적 클래스 이름 |
| Path Allowlist | 예: “/blog/.*” |
| Post to Category | RSS 폴링 카테고리와 동일한 카테고리 |
3. 최종 참고사항
RSS/Atom 피드 폴링과 Discourse 자체는 개별 블로그 포스트에 대해 주제가 존재하지 않는 경우 새 주제를 생성합니다. RSS/Atom 피드 폴링이 먼저 수행되도록 하십시오(즉, 웹사이트에서 블로그 포스트를 방문하기 전에 주제가 생성될 때까지 기다림).
이유: Discourse는 제목과 요약을 크롤링할 수 없으므로, 주제는 mysite.com이 되고 요약을 We are sorry, but this site does not work without javascript.로 설정하게 됩니다. ![]()
만약 어떤 이유로 Discourse가 먼저 실행되었다면, 주제를 삭제하고 RSS/Atom 피드가 작동할 때까지 기다리면 됩니다.
감사합니다
– MK2k