diff --git a/package.json b/package.json
index f74f86c..544e892 100644
--- a/package.json
+++ b/package.json
@@ -33,6 +33,7 @@
     "@swup/scroll-plugin": "^3.3.2",
     "@tailwindcss/vite": "^4.1.2",
     "@waline/client": "^3.5.6",
+    "artalk": "^2.9.1",
     "astro": "^5.6.1",
     "astro-compress": "2.3.5",
     "astro-icon": "^1.1.5",
diff --git a/src/components/Comment.astro b/src/components/Comment.astro
index a9603cf..838c531 100644
--- a/src/components/Comment.astro
+++ b/src/components/Comment.astro
@@ -3,6 +3,7 @@ import { commentConfig } from '@/config';
 import Giscus from '@components/comment/Giscus.astro';
 import Twikoo from '@components/comment/Twikoo.astro';
 import Waline from '@components/comment/Waline.astro';
+import Artalk from './comment/Artalk.astro';
 ---
 
 <div id="page-comment">
@@ -15,6 +16,8 @@ import Waline from '@components/comment/Waline.astro';
           return <Giscus />;
         case 'waline':
           return <Waline />;
+        case 'artalk':
+          return <Artalk />;
       }
     })()
   }
diff --git a/src/components/aside/ResentCommentsCard.astro b/src/components/aside/ResentCommentsCard.astro
index 276d987..266b507 100644
--- a/src/components/aside/ResentCommentsCard.astro
+++ b/src/components/aside/ResentCommentsCard.astro
@@ -2,6 +2,7 @@
 import { asideConfig, commentConfig } from '@/config';
 import I18nKey from '@i18n/I18nKey';
 import { i18n } from '@i18n/translation';
+import ArtalkRecentCommentScript from './recent-comments/Artalk.astro';
 import TwikooRecentCommentScript from './recent-comments/Twikoo.astro';
 import WalineRecentCommentScript from './recent-comments/Waline.astro';
 ---
@@ -50,6 +51,8 @@ import WalineRecentCommentScript from './recent-comments/Waline.astro';
         return <TwikooRecentCommentScript />;
       case 'waline':
         return <WalineRecentCommentScript />;
+      case 'artalk':
+        return <ArtalkRecentCommentScript />;
       default:
         throw new Error(
           `Unsupported comment provider: '${commentConfig.provider}' for recent comments`
diff --git a/src/components/aside/recent-comments/Artalk.astro b/src/components/aside/recent-comments/Artalk.astro
new file mode 100644
index 0000000..0be4eb7
--- /dev/null
+++ b/src/components/aside/recent-comments/Artalk.astro
@@ -0,0 +1,73 @@
+<script>
+  import { asideConfig, commentConfig } from '@/config';
+  import {
+    cleanCommentHtml,
+    cleanPlaceholders,
+    createCommentItem,
+    getTemplate,
+  } from './utils.ts';
+
+  async function setup() {
+    const artalkConfig = commentConfig.artalk!;
+    const commentCount = asideConfig.recentComment.count;
+
+    const apiCommentUrl = new URL(
+      `api/v2/stats/latest_comments?limit=${commentCount}`,
+      artalkConfig.serverURL
+    ).toString();
+    const apiConfigUrl = new URL(`api/v2/conf`, artalkConfig.serverURL).toString();
+
+    const responseComment = fetch(apiCommentUrl, {
+      method: 'GET',
+    });
+    const responseConfig = fetch(apiConfigUrl, {
+      method: 'GET',
+    });
+    if (!(await responseComment).ok) {
+      throw new Error('Failed to fetch recent comments');
+    }
+    if (!(await responseConfig).ok) {
+      throw new Error('Failed to fetch comment config');
+    }
+
+    const commentData: {
+      id: number;
+      nick: string;
+      content_marked: string;
+      date: string;
+      email_encrypted: string;
+      page_url: string;
+    }[] = (await (await responseComment).json()).data;
+    const configData: {
+      gravatar: {
+        mirror: string;
+        params: string;
+      };
+    } = (await (await responseConfig).json()).frontend_conf;
+
+    const getAvatarUrl = (email: string) => {
+      return `${configData.gravatar.mirror}${email}?${configData.gravatar.params}`;
+    };
+
+    const { container, template } = getTemplate()!;
+    if (container && !template) {
+      // 说明已经加载完毕, 模板被删除了
+      return;
+    }
+
+    commentData.forEach((item) =>
+      createCommentItem(container, template!, {
+        avatarUrl: getAvatarUrl(item.email_encrypted),
+        commentContent: cleanCommentHtml(item.content_marked),
+        commentUrl: `${item.page_url}#atk-comment-${item.id}`,
+        author: item.nick,
+        time: new Date(item.date),
+      })
+    );
+
+    cleanPlaceholders(container, template!);
+  }
+
+  document.addEventListener('astro:page-load', setup);
+  await setup();
+</script>
diff --git a/src/components/aside/recent-comments/Waline.astro b/src/components/aside/recent-comments/Waline.astro
index 1c8df9d..29391dc 100644
--- a/src/components/aside/recent-comments/Waline.astro
+++ b/src/components/aside/recent-comments/Waline.astro
@@ -10,8 +10,10 @@
   async function setup() {
     const walineConfig = commentConfig.waline!;
     const commentCount = asideConfig.recentComment.count;
-    const apiUrl = `${walineConfig.serverURL}/api/comment?type=recent&count=${commentCount}`;
-    console.log('apiUrl', apiUrl);
+    const apiUrl = new URL(
+      `api/comment?type=recent&count=${commentCount}`,
+      walineConfig.serverURL
+    ).toString();
 
     const response = await fetch(apiUrl, {
       method: 'GET',
diff --git a/src/components/comment/Artalk.astro b/src/components/comment/Artalk.astro
new file mode 100644
index 0000000..54ee43e
--- /dev/null
+++ b/src/components/comment/Artalk.astro
@@ -0,0 +1,42 @@
+---
+import 'artalk/Artalk.css';
+---
+
+<div id="artalk-wrapper" class="mt-6"></div>
+
+<script>
+  import { commentConfig, siteConfig } from '@/config';
+  import Artalk from 'artalk';
+
+  function setup() {
+    function getCurrentMode(): boolean {
+      if (!('darkMode' in localStorage)) {
+        return window.matchMedia('(prefers-color-scheme: dark)').matches;
+      }
+      return localStorage.darkMode === 'true';
+    }
+
+    const artalk = Artalk.init({
+      el: '#artalk-wrapper',
+      darkMode: getCurrentMode(),
+      pageKey: window.location.pathname,
+      pageTitle: document.title,
+      server: commentConfig.artalk?.serverURL,
+      site: siteConfig.title,
+      locale: commentConfig.artalk?.locale || siteConfig.lang,
+    });
+
+    document.addEventListener('blog:darkmode-change', (e) => {
+      // @ts-expect-error CustomEvent.detail is not defined in TypeScript
+      const { isDark } = e.detail;
+      artalk.setDarkMode(isDark);
+    });
+
+    document.addEventListener('astro:before-swap', () => {
+      artalk.destroy();
+    });
+  }
+
+  document.addEventListener('astro:page-load', setup);
+  setup();
+</script>
diff --git a/src/config.ts b/src/config.ts
index c9cbaf8..4474cae 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -199,4 +199,8 @@ export const commentConfig: CommentConfig = {
     pageSize: 10,
     reaction: false,
   },
+  artalk: {
+    serverURL: 'https://artalk.yourdomain.com',
+    // locale: 'en', // Optional, default is site language.
+  },
 };
diff --git a/src/types/config.ts b/src/types/config.ts
index 5493373..08ac7a0 100644
--- a/src/types/config.ts
+++ b/src/types/config.ts
@@ -498,7 +498,7 @@ export type CommentConfig = {
    *
    * 评论的提供者。
    */
-  provider: 'twikoo' | 'giscus' | 'waline';
+  provider: 'twikoo' | 'giscus' | 'waline' | 'artalk';
   /**
    * The configuration of Twikoo.
    *
@@ -708,4 +708,32 @@ export type CommentConfig = {
      */
     reaction?: boolean | string[];
   };
+  /**
+   * The configuration of Artalk.
+   * Most settings should be configured on the server side,
+   * so only the most basic configuration is provided here.
+   *
+   * Artalk 的配置。大多数设置应当在服务端进行配置,因此这里仅提供最基本的配置。
+   *
+   * @see https://artalk.js.org/
+   */
+  artalk?: {
+    /**
+     * The server URL of Artalk.
+     *
+     * Artalk 的服务端地址。
+     *
+     * @see https://artalk.js.org/en/guide/frontend/config.html#server
+     */
+    serverURL: string;
+    /**
+     * Language setting of Artalk.
+     *
+     * 语言设置
+     *
+     * @default siteConfig.lang
+     * @see https://artalk.js.org/en/guide/frontend/config.html#locale
+     */
+    locale?: string;
+  };
 };