博客评论系统,我选了 Twikoo

6 min

comments 博客没有评论系统,总感觉少了点什么。读者看了文章想聊两句,却找不到地方说,这种体验确实不太好。

之前试过几个评论系统,要么太臃肿,要么配置麻烦,要么加载太慢。后来发现了 Twikoo,用下来感觉还不错,今天就分享一下怎么把它接到 Astro 项目里。

部署 Twikoo

Twikoo 部署挺简单的,推荐用 Vercel,几分钟就能搞定。具体步骤看官方文档就行,这里就不多说了。

创建评论组件

在 Astro 项目里新建一个组件文件,路径是 src/components/TwikooComments.astro

---
interface Props {
  envId: string;
  path?: string;
}

const { envId, path } = Astro.props;
---

<div class="mt-8">
  <h2 class="mb-4 text-lg font-medium text-zinc-900 dark:text-zinc-100">评论</h2>
  <div 
    id="tcomment" 
    class="twikoo-container rounded-lg border p-4 dark:border-zinc-700"
    data-env-id={envId}
    data-path={path || 'auto'}
  >
    <div class="flex items-center justify-center py-8 text-zinc-500 dark:text-zinc-400">
      <div class="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-500 mr-2"></div>
      正在加载评论...
    </div>
  </div>
</div>

<script 
  src="https://s4.zstatic.net/npm/twikoo@1.6.44/dist/twikoo.min.js"
  defer
></script>

<script is:inline>
  window.twikooRetryCount = 0;
  window.maxRetries = 20;

  function waitForTwikoo(callback, retryCount = 0) {
    if (typeof window.twikoo !== 'undefined') {
      callback();
    } else if (retryCount < window.maxRetries) {
      setTimeout(() => waitForTwikoo(callback, retryCount + 1), 200);
    } else {
      console.error('Twikoo failed to load after maximum retries');
      showErrorMessage();
    }
  }

  function showErrorMessage() {
    const containers = document.querySelectorAll('#tcomment');
    containers.forEach(container => {
      if (container) {
        container.innerHTML = `
          <div class="text-center py-8 text-red-500">
            <p>评论系统加载失败</p>
            <button onclick="location.reload()" class="mt-2 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 text-sm">
              重新加载
            </button>
          </div>
        `;
      }
    });
  }

  function initializeTwikoo() {
    const container = document.getElementById('tcomment');
    
    if (!container) {
      console.warn('Twikoo container (#tcomment) not found');
      return;
    }

    const envId = container.getAttribute('data-env-id');
    const path = container.getAttribute('data-path');
    
    if (!envId) {
      console.error('Twikoo envId not provided');
      container.innerHTML = '<div class="text-center py-8 text-red-500">评论系统配置错误</div>';
      return;
    }

    try {
      container.innerHTML = '';
      
      window.twikoo.init({
        envId: envId,
        el: '#tcomment',
        path: path === 'auto' ? location.pathname : path,
        lang: 'zh-CN'
      });
      
      console.log('Twikoo initialized successfully');
    } catch (error) {
      console.error('Failed to initialize Twikoo:', error);
      container.innerHTML = `
        <div class="text-center py-8 text-red-500">
          <p>评论系统初始化失败</p>
          <p class="text-sm mt-1">${error.message}</p>
          <button onclick="location.reload()" class="mt-2 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 text-sm">
            重新加载
          </button>
        </div>
      `;
    }
  }

  function handlePageInit() {
    window.twikooRetryCount = 0;
    waitForTwikoo(initializeTwikoo);
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', handlePageInit);
  } else {
    setTimeout(handlePageInit, 100);
  }

  document.addEventListener('astro:after-swap', () => {
    console.log('Page swapped, reinitializing Twikoo');
    setTimeout(handlePageInit, 150);
  });

  document.addEventListener('visibilitychange', () => {
    if (!document.hidden) {
      const container = document.getElementById('tcomment');
      if (container && container.children.length === 0) {
        console.log('Page became visible, checking Twikoo');
        setTimeout(handlePageInit, 100);
      }
    }
  });
</script>

<style is:global>
  .twikoo-container {
    font-family: inherit;
    min-height: 200px;
  }
  
  .dark .twikoo-container {
    background-color: transparent;
  }
  
  .dark .tk-content textarea,
  .dark .tk-input input {
    background-color: rgb(39 39 42) !important;
    border-color: rgb(63 63 70) !important;
    color: rgb(228 228 231) !important;
  }
  
  .dark .tk-content textarea:focus,
  .dark .tk-input input:focus {
    border-color: rgb(96 165 250) !important;
  }
  
  .dark .tk-submit {
    background-color: rgb(24 24 27) !important;
    border-color: rgb(63 63 70) !important;
    color: rgb(228 228 231) !important;
  }
  
  .dark .tk-submit:hover {
    background-color: rgb(39 39 42) !important;
  }
  
  .dark .tk-comment,
  .dark .tk-replies-wrap {
    background-color: transparent !important;
    border-color: rgb(63 63 70) !important;
  }
  
  .dark .tk-comment .tk-main {
    color: rgb(228 228 231) !important;
  }
  
  .dark .tk-comment .tk-meta span {
    color: rgb(161 161 170) !important;
  }
  
  .dark .tk-comment a {
    color: rgb(96 165 250) !important;
  }
  
  .dark .tk-comment a:hover {
    color: rgb(147 197 253) !important;
  }
  
  .dark .tk-owo-container {
    background-color: rgb(39 39 42) !important;
    border-color: rgb(63 63 70) !important;
  }
  
  .dark .tk-tag,
  .dark .tk-extras {
    color: rgb(161 161 170) !important;
  }
  
  .tk-comment,
  .tk-content,
  .tk-input {
    font-family: 'Geist', system-ui, sans-serif !important;
  }
  
  .tk-content textarea,
  .tk-input input,
  .tk-submit,
  .tk-comment,
  .tk-owo-container {
    border-radius: 0.5rem !important;
  }
  
  .tk-comment {
    margin-bottom: 1rem !important;
  }
  
  .dark .tk-loading {
    color: rgb(161 161 170) !important;
  }
  
  .animate-spin {
    animation: spin 1s linear infinite;
  }
  
  @keyframes spin {
    from { transform: rotate(0deg); }
    to { transform: rotate(360deg); }
  }
</style>

在文章页面使用

把组件加到博客文章模板里,路径是 src/pages/blog/[id].astro

顶部引入组件:

---
import TwikooComments from '../../components/TwikooComments.astro';
---

在合适的位置插入评论组件:

<TwikooComments 
  envId="your-twikoo-env-id" 
  path={`/posts/${post.slug}`}
/>

your-twikoo-env-id 换成你自己的 Twikoo 环境 ID。

评论开关

有些文章可能不想开评论,比如技术文档或者个人日记。可以给每篇文章加个开关:

src/content.config.ts 里添加配置:

import { defineCollection, z } from 'astro:content';

const blogCollection = defineCollection({
  schema: z.object({
    title: z.string(),
    pubDate: z.date(),
    description: z.string(),
    comments: z.boolean().default(true),
  }),
});

然后在文章模板里判断:

---
const { data, content } = Astro.props;
const { comments = true } = data;
---

{comments && (
  <section class="comments-section">
    <h2>评论</h2>
    <TwikooComments envId="YOUR_TWIKOO_ENV_ID" path={Astro.url.pathname} />
  </section>
)}

写文章的时候在 frontmatter 里控制:

---
title: "我的博客文章"
comments: false
---

评论关闭提示

评论关了总得告诉访客一声,加个友好的提示:

src/components/CommentsDisabled.astro 里添加:

---
---

<div class="comments-disabled-notice">
  <div class="comments-disabled-icon">
    <i class="iconoir-chat-bubble-block"></i>
  </div>
  <div class="comments-disabled-text">
    <h3>评论已关闭</h3>
    <p>本文评论功能已关闭。如果您有任何问题或建议,可以通过其他方式与我联系。</p>
  </div>
</div>

<style>
  .comments-disabled-notice {
    display: flex;
    align-items: flex-start;
    gap: 0.75rem;
    padding: 1.5rem;
    margin-top: 2rem;
    background-color: var(--bg-color-secondary);
    border-radius: 8px;
    border-left: 4px solid #ef4444;
  }
  
  .comments-disabled-icon {
    color: #ef4444;
    font-size: 0.9rem;
    margin-top: 0.2rem;
    flex-shrink: 0;
    width: 24px;
    text-align: center;
  }
  
  .comments-disabled-text h3 {
    margin: 0 0 0.5rem 0;
    font-size: 1.1rem;
    color: var(--text-color);
  }
  
  .comments-disabled-text p {
    margin: 0;
    font-size: 0.95rem;
    color: var(--text-color-secondary);
    line-height: 1.5;
  }
</style>

一些小技巧

自定义样式

Twikoo 默认样式可能不太搭你的网站,可以自己改 CSS:

:root {
  --tk-theme-color: #4a90e2;
  --tk-meta-color: #666;
  --tk-border-color: #e0e0e0;
}

#tcomment .tk-input {
  border-radius: 8px;
  border: 1px solid var(--tk-border-color);
  padding: 0.75rem;
}

评论通知

有人评论了想第一时间知道?Twikoo 支持邮件、微信、钉钉通知,在管理面板里设置就行。

延迟加载

如果页面加载速度很重要,可以等用户点击再加载评论:

<div id="twikoo-container">
  <button onclick="loadComments()">加载评论</button>
  <div id="tcomment" style="display: none;"></div>
</div>

常见问题

  • 评论不显示:检查 envId 对不对,Twikoo 服务是否正常
  • 样式乱了:看看有没有 CSS 冲突
  • 加载慢:试试延迟加载

最后

Twikoo 用起来确实挺省心的,配置简单,功能也够用。如果你也在找评论系统,可以试试这个。