<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>cusdis on Kohsruhe</title><link>https://www.kohsruhe.com/zh/tag/cusdis/</link><description>Recent content in cusdis on Kohsruhe</description><generator>Hugo -- gohugo.io</generator><language>zh</language><managingEditor>kohsruhe@outlook.com (Leehyon HNG)</managingEditor><webMaster>kohsruhe@outlook.com (Leehyon HNG)</webMaster><lastBuildDate>Sat, 22 Aug 2026 13:20:00 +0800</lastBuildDate><atom:link href="https://www.kohsruhe.com/zh/tag/cusdis/index.xml" rel="self" type="application/rss+xml"/><item><title>从 Cusdis 到 Remark42：博客评论系统迁移记</title><link>https://www.kohsruhe.com/zh/2026/08/from-cusdis-to-remark42/</link><pubDate>Sat, 22 Aug 2026 13:20:00 +0800</pubDate><author>kohsruhe@outlook.com (Leehyon HNG)</author><guid>https://www.kohsruhe.com/zh/2026/08/from-cusdis-to-remark42/</guid><description>&lt;h2 id="起因"&gt;起因&lt;/h2&gt;
&lt;p&gt;博客之前的评论系统是参考了 &lt;a href="https://www.pseudoyu.com/zh/2022/05/24/free_and_lightweight_blog_comment_system_using_cusdis_and_railway"&gt;Pseudoyu&lt;/a&gt; 的教程做的。Cusdis 整体使用下来很轻量，界面也很简约，挺适合我这种无人问津的小站。但有几个不太舒服的点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;数据托管在官方的 Free Plan，功能有限，有时候会抽风打不开&lt;/li&gt;
&lt;li&gt;后台比较简陋，缺少基本的反垃圾和通知能力&lt;/li&gt;
&lt;li&gt;评论需要管理员手动审核，经常错过评论和及时回复&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但真正让我下决心换掉的是这个 iframe bug：评论区被压成一个小框带滚动条，要手动拉才能看到完整内容。issue &lt;a href="https://github.com/djyde/cusdis/issues/283"&gt;#283&lt;/a&gt; 开了快 2 年没修，社区的 workaround 都是手动 JS 重设 iframe 高度或者 MutationObserver 监听 resize，相当 hacky；连 Cusdis 自己的 &lt;a href="https://cusdis.com/doc#/faq"&gt;官网&lt;/a&gt; 也中招。&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="起因">起因</h2>
<p>博客之前的评论系统是参考了 <a href="https://www.pseudoyu.com/zh/2022/05/24/free_and_lightweight_blog_comment_system_using_cusdis_and_railway">Pseudoyu</a> 的教程做的。Cusdis 整体使用下来很轻量，界面也很简约，挺适合我这种无人问津的小站。但有几个不太舒服的点：</p>
<ul>
<li>数据托管在官方的 Free Plan，功能有限，有时候会抽风打不开</li>
<li>后台比较简陋，缺少基本的反垃圾和通知能力</li>
<li>评论需要管理员手动审核，经常错过评论和及时回复</li>
</ul>
<p>但真正让我下决心换掉的是这个 iframe bug：评论区被压成一个小框带滚动条，要手动拉才能看到完整内容。issue <a href="https://github.com/djyde/cusdis/issues/283">#283</a> 开了快 2 年没修，社区的 workaround 都是手动 JS 重设 iframe 高度或者 MutationObserver 监听 resize，相当 hacky；连 Cusdis 自己的 <a href="https://cusdis.com/doc#/faq">官网</a> 也中招。</p>
<p><img src="https://images.kohsruhe.com/2026/cusdis_iframe_issue.png" alt="cusdis_iframe_issue"></p>
<h2 id="选型">选型</h2>
<p>对评论系统的要求很简单：</p>
<ul>
<li><strong>自托管</strong>，数据在自己手里</li>
<li><strong>轻量</strong>，单二进制 / 单容器就能跑</li>
<li><strong>支持 OAuth 或者匿名登录</strong>，别让访客为了评论再注册一个账号</li>
<li><strong>评论通知</strong>，及时阅读和回复</li>
</ul>
<p>也是看了 <a href="https://www.pseudoyu.com/zh/2024/07/22/free_commenting_system_using_remark42_and_flyio">Pseudoyu</a> 的文章选的 Remark42：</p>
<ul>
<li>单容器部署，bolt 文件存储，零依赖</li>
<li>内置 GitHub、Google、Twitter、Telegram 等十几种 OAuth</li>
<li>评论数据存在本地 bolt 文件</li>
<li>自带 admin 后台，OAuth 登录白名单管理</li>
</ul>
<p>不过 <a href="https://fly.io/">fly.io</a> 已经没有免费计划了，所以也一直没迁移过来。直到最近折腾了 Cloudflare Tunnel，确实可以托管在自家的 NAS 上然后暴露到公网，这样一来就更方便了，搞起。</p>
<h2 id="整体架构">整体架构</h2>
<pre class="mermaid">flowchart TB
    User[&#34;公网用户&#34;]
    Edge[&#34;Cloudflare Edge&lt;br/&gt;域名、HTTPS、安全防护&#34;]

    subgraph Home[&#34;家庭内网 · 无公网 IP&#34;]
        Tunnel[&#34;cloudflared&lt;br/&gt;Docker 项目 A&#34;]
        Service[&#34;Remark42&lt;br/&gt;Docker 项目 B&#34;]
    end

    User --&gt;|&#34;访问 comments.kohsruhe.com&#34;| Edge
    Tunnel ==&gt;|&#34;主动建立加密隧道&#34;| Edge
    Edge --&gt;|&#34;通过隧道转发请求&#34;| Tunnel
    Tunnel --&gt;|&#34;Docker 共享网络&lt;br/&gt;remark42:8080&#34;| Service</pre><blockquote>
<p>对比以 FRP 一类的公网中转方案，主要区别是 Cloudflare Tunnel 使用 Cloudflare Edge 作为公网入口，并集成域名、HTTPS 和安全能力，而一般内网穿透通常依赖一台自行维护的公网中转服务器及端口映射。</p>
</blockquote>
<h2 id="部署-remark42">部署 Remark42</h2>
<p>我使用 Docker Compose 的方式部署，直接在绿联 NAS 中新建一个 Docker 项目，并将下面的配置保存为项目的 <code>docker-compose.yml</code> 文件：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">remark42</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">ghcr.io/umputun/remark42:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;remark42&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">hostname</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;remark42&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">always</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">logging</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">driver</span><span class="p">:</span><span class="w"> </span><span class="l">json-file</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">options</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">max-size</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;10m&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">max-file</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;5&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;8080:8080&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># Basic setup</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">REMARK_URL</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;https://comments.kohsruhe.com&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">SITE</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;kohsruhe&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">SECRET</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;&lt;random-long-string&gt;&#34;</span><span class="w"> </span><span class="c"># openssl rand -hex 32</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">DEBUG</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;false&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">ALLOWED_HOSTS</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;localhost,www.kohsruhe.com&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">AUTH_SAME_SITE</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;lax&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">AUTH_ANON</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;true&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># Enable Email login</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">AUTH_EMAIL_ENABLE</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;true&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">AUTH_EMAIL_FROM</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;&#34;Remark42 Login&#34; &lt;kohsruhe@gmail.com&gt;&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">AUTH_EMAIL_SUBJ</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Confirm your Remark42 login&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">AUTH_EMAIL_CONTENT_TYPE</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;text/html&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># GitHub setup</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">AUTH_GITHUB_CID</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;&lt;from-github&gt;&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">AUTH_GITHUB_CSEC</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;&lt;from-github&gt;&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">ADMIN_SHARED_ID</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;github_&lt;your-user-id&gt;&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># Gmail SMTP setup</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">SMTP_HOST</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;smtp.gmail.com&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">SMTP_PORT</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;465&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">SMTP_TLS</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;true&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">SMTP_STARTTLS</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;false&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">SMTP_USERNAME</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;kohsruhe@gmail.com&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">SMTP_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;&lt;16-digit passcode&gt;&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">SMTP_TIMEOUT</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;10s&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">SMTP_INSECURE_SKIP_VERIFY</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;false&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># Notify users</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">NOTIFY_USERS</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;email&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">NOTIFY_EMAIL_FROM</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;&#34;Remark42 Notifications&#34; &lt;kohsruhe@gmail.com&gt;&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">NOTIFY_EMAIL_VERIFICATION_SUBJ</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Confirm your comment subscription&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">NOTIFY_ADMINS</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;slack&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">NOTIFY_SLACK_CHAN</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;remark42&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">NOTIFY_SLACK_TOKEN</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;xoxb-&lt;your-slack-token&gt;&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./var:/srv/var</span><span class="w">
</span></span></span></code></pre></div><ul>
<li><code>./var</code> 目录用来存 bolt 数据和图片，建议挂到 NAS 的持久化卷</li>
<li>消息通知我用的 <a href="https://remark42.com/docs/configuration/notifications/">Slack</a>，Telegram 换手机后就寄了</li>
<li>配置项太多了，参考官方说明 <a href="https://remark42.com/docs/configuration/parameters/">parameters</a></li>
</ul>
<h2 id="配-github-oauth">配 GitHub OAuth</h2>
<p>去 <a href="https://github.com/settings/developers">GitHub Developer Settings</a> → New OAuth App：</p>
<ul>
<li>Application name: <code>Kohsruhe Comments</code></li>
<li>Homepage URL: <code>https://www.kohsruhe.com</code></li>
<li>Authorization callback URL: <code>https://comments.kohsruhe.com/auth/github/callback</code></li>
</ul>
<p>注册后拿到 Client ID 和 Client Secret，填到上面 <code>AUTH_GITHUB_CID</code> / <code>AUTH_GITHUB_CSEC</code>。</p>
<h2 id="启动并配置-admin">启动并配置 admin</h2>
<p>第一次启动先不设 admin，先把 Remark42 跑起来。</p>
<p>在博客页用 GitHub 登录一次评论系统，点用户名右侧展开会显示完整的 user ID（<code>github_</code> 开头的一长串 hex），复制这段。</p>
<p><img src="https://images.kohsruhe.com/2026/github_user_id.png" alt="github_user_id"></p>
<p>然后停掉容器，env 里加上 <code>ADMIN_SHARED_ID=github_&lt;your-user-id&gt;</code>，重启即可。</p>
<h2 id="集成到-hugo">集成到 Hugo</h2>
<p>在 <code>layouts/partials/remark42.html</code> 里：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;comments&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;title&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">span</span><span class="p">&gt;</span>Comments<span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">span</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;counter&#34;</span><span class="p">&gt;&lt;</span><span class="nt">span</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;remark42__counter&#34;</span> <span class="na">data-url</span><span class="o">=</span><span class="s">&#34;{{ .Permalink }}&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">span</span><span class="p">&gt;&lt;/</span><span class="nt">span</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">div</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;remark42&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="kd">var</span> <span class="nx">remark_config</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">host</span><span class="o">:</span> <span class="s1">&#39;https://comments.kohsruhe.com&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">site_id</span><span class="o">:</span> <span class="s1">&#39;kohsruhe&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">components</span><span class="o">:</span> <span class="p">[</span><span class="s1">&#39;embed&#39;</span><span class="p">,</span> <span class="s1">&#39;counter&#39;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="nx">max_shown_comments</span><span class="o">:</span> <span class="mi">20</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">simple_view</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">theme</span><span class="o">:</span> <span class="s1">&#39;light&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">;(</span><span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// init or reset remark42
</span></span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">remark42</span> <span class="o">=</span> <span class="nb">window</span><span class="p">.</span><span class="nx">REMARK42</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">remark42</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">remark42</span><span class="p">.</span><span class="nx">destroy</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">      <span class="nx">remark42</span><span class="p">.</span><span class="nx">createInstance</span><span class="p">(</span><span class="nx">remark_config</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="k">for</span> <span class="p">(</span><span class="kr">const</span> <span class="nx">component</span> <span class="k">of</span> <span class="nx">remark_config</span><span class="p">.</span><span class="nx">components</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kd">var</span> <span class="nx">d</span> <span class="o">=</span> <span class="nb">document</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nx">s</span> <span class="o">=</span> <span class="nx">d</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s1">&#39;script&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="nx">s</span><span class="p">.</span><span class="nx">src</span> <span class="o">=</span> <span class="sb">`</span><span class="si">${</span><span class="nx">remark_config</span><span class="p">.</span><span class="nx">host</span><span class="si">}</span><span class="sb">/web/</span><span class="si">${</span><span class="nx">component</span><span class="si">}</span><span class="sb">.mjs`</span>
</span></span><span class="line"><span class="cl">        <span class="nx">s</span><span class="p">.</span><span class="nx">type</span> <span class="o">=</span> <span class="s1">&#39;module&#39;</span>
</span></span><span class="line"><span class="cl">        <span class="nx">s</span><span class="p">.</span><span class="nx">defer</span> <span class="o">=</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// prevent the &lt;script&gt; from loading mutiple times by InstantClick
</span></span></span><span class="line"><span class="cl">        <span class="nx">s</span><span class="p">.</span><span class="nx">setAttribute</span><span class="p">(</span><span class="s1">&#39;data-no-instant&#39;</span><span class="p">,</span> <span class="s1">&#39;&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="nx">d</span><span class="p">.</span><span class="nx">head</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">s</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">})()</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>同时在 <code>layouts/posts/single.html</code> 末尾 <code>{{ partial &quot;remark42.html&quot; . }}</code> 引用即可。</p>
<h2 id="总结">总结</h2>
<p>整个迁移的代码量比想象中少：Remark42 单 docker 镜像、GitHub OAuth 一个 callback URL 就完事。真正花时间的是 Cloudflare Tunnel 的配置，那部分单独写一篇。</p>
<p>感恩赛博菩萨! 🙏</p>
]]></content:encoded></item></channel></rss>