博客评论系统,我选了 Twikoo
6 min
博客没有评论系统,总感觉少了点什么。读者看了文章想聊两句,却找不到地方说,这种体验确实不太好。
之前试过几个评论系统,要么太臃肿,要么配置麻烦,要么加载太慢。后来发现了 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 用起来确实挺省心的,配置简单,功能也够用。如果你也在找评论系统,可以试试这个。