diff --git a/README.md b/README.md
index 3a9f185..4966add 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# AsyncTalk - 和我们一起,把 web 开发带向下一个高度
+# AsyncTalk - 和我们一起,将 Web 开发带向下一个高度
AsyncTalk 是一档以中文(华语)讨论 Web 开发技术的播客节目。
diff --git a/astro.config.mjs b/astro.config.mjs
index 76c2571..7190e82 100644
--- a/astro.config.mjs
+++ b/astro.config.mjs
@@ -8,7 +8,8 @@ import react from "@astrojs/react";
// https://astro.build/config
export default defineConfig({
- site: "https://AsyncTalk.com",
+ site: "https://asynctalk.com/",
+ trailingSlash: "always",
compressHTML: true,
vite: {
plugins: [tailwindcss()],
diff --git a/package.json b/package.json
index 052ac45..5a8d13b 100644
--- a/package.json
+++ b/package.json
@@ -9,10 +9,11 @@
"scripts": {
"dev": "astro dev",
"start": "astro dev",
- "build": "astro check && astro build",
+ "build": "astro check && astro build && pnpm seo:check",
"generate:thumbnail": "tsx scripts/generate-thumbnail.ts",
"preview": "astro preview",
- "astro": "astro"
+ "astro": "astro",
+ "seo:check": "node scripts/check-seo.mjs"
},
"dependencies": {
"@astro-community/astro-embed-youtube": "^0.5.10",
diff --git a/public/robots.txt b/public/robots.txt
index ce6cc1f..756ecd8 100644
--- a/public/robots.txt
+++ b/public/robots.txt
@@ -1,8 +1,4 @@
User-agent: *
Allow: /
-Allow: /posts/
-Allow: /rss.xml
-Disallow: /og-preview
-Disallow: /posts/*-og.png
Sitemap: https://asynctalk.com/sitemap-index.xml
diff --git a/public/site.webmanifest b/public/site.webmanifest
index 45dc8a2..25f9448 100644
--- a/public/site.webmanifest
+++ b/public/site.webmanifest
@@ -1 +1,22 @@
-{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
\ No newline at end of file
+{
+ "name": "AsyncTalk Podcast",
+ "short_name": "AsyncTalk",
+ "description": "关注 Web 开发、前端工程化与 AI 的中文播客",
+ "lang": "zh-CN",
+ "start_url": "/",
+ "icons": [
+ {
+ "src": "/android-chrome-192x192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ },
+ {
+ "src": "/android-chrome-512x512.png",
+ "sizes": "512x512",
+ "type": "image/png"
+ }
+ ],
+ "theme_color": "#000000",
+ "background_color": "#000000",
+ "display": "standalone"
+}
diff --git a/scripts/check-seo.mjs b/scripts/check-seo.mjs
new file mode 100644
index 0000000..667d769
--- /dev/null
+++ b/scripts/check-seo.mjs
@@ -0,0 +1,188 @@
+import { readdir, readFile } from "node:fs/promises";
+import path from "node:path";
+
+const projectRoot = path.resolve(import.meta.dirname, "..");
+const distDir = path.join(projectRoot, "dist");
+const contentDir = path.join(projectRoot, "src/content/posts");
+const failures = [];
+
+function expect(condition, message) {
+ if (!condition) {
+ failures.push(message);
+ }
+}
+
+function matches(value, pattern) {
+ return [...value.matchAll(pattern)];
+}
+
+function attribute(tag, name) {
+ return tag.match(new RegExp(`${name}=["']([^"']+)["']`, "i"))?.[1];
+}
+
+async function listFiles(directory) {
+ const entries = await readdir(directory, { withFileTypes: true });
+ const nested = await Promise.all(
+ entries.map((entry) => {
+ const target = path.join(directory, entry.name);
+ return entry.isDirectory() ? listFiles(target) : [target];
+ }),
+ );
+ return nested.flat();
+}
+
+const contentFiles = (await readdir(contentDir))
+ .filter((file) => file.endsWith(".mdx"))
+ .sort();
+const publishedEpisodes = [];
+const excerpts = [];
+
+for (const file of contentFiles) {
+ const source = await readFile(path.join(contentDir, file), "utf8");
+ const status = source.match(/^status:\s*(.+)$/m)?.[1]?.trim();
+ const excerpt = source
+ .match(/^excerpt:\s*(.+)$/m)?.[1]
+ ?.trim()
+ .replace(/^"|"$/g, "");
+
+ if (status === "published") {
+ publishedEpisodes.push(file.replace(/\.mdx$/, ""));
+ }
+
+ expect(Boolean(excerpt), `${file}: missing excerpt`);
+ if (excerpt) {
+ const length = [...excerpt].length;
+ expect(length >= 40 && length <= 160, `${file}: excerpt length is ${length}`);
+ excerpts.push(excerpt);
+ }
+}
+
+expect(
+ new Set(excerpts).size === excerpts.length,
+ "Episode excerpts must be unique",
+);
+
+const htmlFiles = (await listFiles(distDir)).filter((file) => file.endsWith(".html"));
+expect(
+ htmlFiles.length === publishedEpisodes.length + 2,
+ `Expected ${publishedEpisodes.length + 2} public HTML pages, found ${htmlFiles.length}`,
+);
+
+for (const file of htmlFiles) {
+ const html = await readFile(file, "utf8");
+ const label = path.relative(distDir, file);
+ const titles = matches(html, /
[^<]+<\/title>/gi);
+ const descriptions = matches(
+ html,
+ /]*>/gi,
+ );
+ const canonicals = matches(html, /]*>/gi);
+ const h1s = matches(html, /)/gi);
+ const ogUrls = matches(html, /]*>/gi);
+ const ogImages = matches(html, /]*>/gi);
+
+ expect(/]*>([\s\S]*?)<\/script>/gi,
+ );
+ const isHome = label === "index.html";
+ const isEpisode = label.startsWith(`posts${path.sep}`) && label !== path.join("posts", "index.html");
+
+ if (isHome || isEpisode) {
+ expect(jsonLdBlocks.length === 1, `${label}: expected one JSON-LD block`);
+ if (jsonLdBlocks[0]) {
+ try {
+ const data = JSON.parse(jsonLdBlocks[0][1]);
+ expect(data["@context"] === "https://schema.org", `${label}: invalid JSON-LD context`);
+ if (isHome) {
+ const types = new Set(data["@graph"]?.map((node) => node["@type"]));
+ expect(types.has("WebSite"), `${label}: missing WebSite data`);
+ expect(types.has("PodcastSeries"), `${label}: missing PodcastSeries data`);
+ expect(types.has("Organization"), `${label}: missing Organization data`);
+ } else {
+ expect(data["@type"] === "PodcastEpisode", `${label}: missing PodcastEpisode data`);
+ expect(Boolean(data.description), `${label}: PodcastEpisode description missing`);
+ expect(Boolean(data.datePublished), `${label}: PodcastEpisode date missing`);
+ }
+ } catch (error) {
+ failures.push(`${label}: invalid JSON-LD (${error.message})`);
+ }
+ }
+ }
+}
+
+const sitemap = await readFile(path.join(distDir, "sitemap-0.xml"), "utf8");
+const sitemapUrls = matches(sitemap, /([^<]+)<\/loc>/g).map((match) => match[1]);
+expect(
+ sitemapUrls.length === publishedEpisodes.length + 2,
+ `Expected ${publishedEpisodes.length + 2} sitemap URLs, found ${sitemapUrls.length}`,
+);
+expect(
+ sitemapUrls.every((url) => url.startsWith("https://asynctalk.com/") && url.endsWith("/")),
+ "Sitemap URLs must be absolute and use trailing slashes",
+);
+expect(!sitemap.includes("index-legacy"), "Legacy page must not appear in sitemap");
+expect(!sitemap.includes("og-preview"), "OG preview must not appear in sitemap");
+
+const rss = await readFile(path.join(distDir, "rss.xml"), "utf8");
+const rssItems = matches(rss, /- [\s\S]*?<\/item>/g);
+expect(
+ rssItems.length === publishedEpisodes.length,
+ `Expected ${publishedEpisodes.length} RSS items, found ${rssItems.length}`,
+);
+for (const [index, item] of rssItems.entries()) {
+ expect(//.test(item[0]), `RSS item ${index + 1}: description missing`);
+ expect(//.test(item[0]), `RSS item ${index + 1}: guid missing`);
+ expect(//.test(item[0]), `RSS item ${index + 1}: author missing`);
+}
+
+const robots = await readFile(path.join(distDir, "robots.txt"), "utf8");
+expect(!/Disallow:\s*\/posts\/\*-og\.png/.test(robots), "robots.txt blocks OG images");
+expect(
+ robots.includes("Sitemap: https://asynctalk.com/sitemap-index.xml"),
+ "robots.txt sitemap URL is missing",
+);
+
+if (failures.length > 0) {
+ console.error(`SEO validation failed with ${failures.length} issue(s):`);
+ for (const failure of failures) {
+ console.error(`- ${failure}`);
+ }
+ process.exitCode = 1;
+} else {
+ console.log(
+ `SEO validation passed: ${htmlFiles.length} HTML pages, ${sitemapUrls.length} sitemap URLs, ${rssItems.length} RSS items.`,
+ );
+}
diff --git a/src/components/OpenGraph/OG.tsx b/src/components/OpenGraph/OG.tsx
index 76e21e6..8cb206f 100644
--- a/src/components/OpenGraph/OG.tsx
+++ b/src/components/OpenGraph/OG.tsx
@@ -7,11 +7,10 @@ const logo = `data:image/png;base64,${readFileSync(
path.resolve("./src/images/logo.png"),
).toString("base64")}`
-const titleFontSize = 3 * ratio
const descriptionFontSize = 1.6 * ratio
const logoSize = 146 * ratio
export default function OG({
- title = "AsyncTalk - 和我们一起,将 Web 开发带向下一个高度",
+ title = "AsyncTalk|和我们一起,将 Web 开发带向下一个高度",
ep,
sp
}: {
@@ -21,6 +20,8 @@ export default function OG({
heroImageURL?: string
}
) {
+ const titleFontSize = (title.length > 30 ? 2 : title.length > 20 ? 2.4 : 3) * ratio
+
return (
{title}
-
+
Async Talk (asynctalk.com)
-
- -
-
-
- {ep ? 'Episode' : 'Special'} {ep ?? sp}
-
+ {(ep !== undefined || sp !== undefined) && (
+ <>
+
+ -
+
+
+ {ep !== undefined ? 'Episode' : 'Special'} {ep ?? sp}
+
+ >
+ )}
);
-}
\ No newline at end of file
+}
diff --git a/src/components/Player.astro b/src/components/Player.astro
index b87d763..ab28548 100644
--- a/src/components/Player.astro
+++ b/src/components/Player.astro
@@ -39,7 +39,7 @@ const { link, title } = Astro.props;
立即播放
- {title}
+ {title}
diff --git a/src/components/WebMCP.astro b/src/components/WebMCP.astro
index f1639c9..b0e8e2b 100644
--- a/src/components/WebMCP.astro
+++ b/src/components/WebMCP.astro
@@ -1,5 +1,6 @@
---
import { getCollection } from "astro:content";
+import { SITE_URL, episodePath } from "../lib/seo";
type EpisodeToolData = {
id: string;
@@ -17,8 +18,11 @@ type EpisodeToolData = {
preview: string;
};
-const siteUrl = Astro.site?.toString().replace(/\/$/, "") ?? "https://asynctalk.com";
-const posts = await getCollection("posts");
+const siteUrl = Astro.site?.toString() ?? SITE_URL;
+const allPosts = await getCollection("posts");
+const posts = import.meta.env.PROD
+ ? allPosts.filter((post) => post.data.status === "published")
+ : allPosts;
function normalizeId(id: string) {
return id.replace(/^\/?posts\//, "").replace(/\.mdx$/, "");
@@ -36,19 +40,6 @@ function normalizeExternalUrl(url: string | undefined) {
return url;
}
-function stripMarkdown(value: string) {
- return value
- .replace(/^---[\s\S]*?---/, "")
- .replace(/```[\s\S]*?```/g, " ")
- .replace(/`([^`]+)`/g, "$1")
- .replace(/!\[[^\]]*]\([^)]*\)/g, " ")
- .replace(/\[([^\]]+)]\([^)]*\)/g, "$1")
- .replace(/[#>*_~\-]+/g, " ")
- .replace(/\s+/g, " ")
- .trim()
- .slice(0, 500);
-}
-
function serializeForInlineScript(value: unknown) {
return JSON.stringify(value).replace(/((post) => {
const id = normalizeId(post.id);
- const slug = `/posts/${id}`;
+ const slug = episodePath(post.id);
const youtubeId = post.data.youtubeId ?? null;
return {
@@ -72,7 +63,7 @@ const episodes = posts
youtubeId,
youtubeUrl: youtubeId ? `https://www.youtube.com/watch?v=${youtubeId}` : null,
biliUrl: normalizeExternalUrl(post.data.biliUrl),
- preview: stripMarkdown(post.body ?? ""),
+ preview: post.data.excerpt,
};
})
.sort((a, b) => {
@@ -120,13 +111,13 @@ const subscriptionLinks = {
return "";
}
- return value.trim().replace(/^\\/?posts\\//, "").replace(/^\\//, "");
+ return value.trim().split("/").filter(Boolean).at(-1) || "";
};
const findEpisode = (input) => {
const id = normalizeEpisodeId(input.id || input.slug);
return episodes.find((episode) => {
- return episode.id === id || episode.slug === input.slug || episode.slug === \`/posts/\${id}\`;
+ return episode.id === id || episode.slug === input.slug;
});
};
const formatEpisode = (episode) => ({
diff --git a/src/components/header.astro b/src/components/header.astro
index af3fd09..e4b7a0d 100644
--- a/src/components/header.astro
+++ b/src/components/header.astro
@@ -80,7 +80,7 @@ const channels = [
diff --git a/src/components/seo/seo-tags.astro b/src/components/seo/seo-tags.astro
index 87a15a9..1e5d390 100644
--- a/src/components/seo/seo-tags.astro
+++ b/src/components/seo/seo-tags.astro
@@ -1,55 +1,73 @@
---
-import logo from "../../images/logo.png";
-
-export enum TwitterCardType {
- summary = "summary",
- summaryLargeImage = "summary_large_image",
-}
+import {
+ OG_LOCALE,
+ SITE_NAME,
+ canonicalUrl,
+ compactUrls,
+} from "../../lib/seo";
type Props = {
title: string;
- desc: string;
- urlPath: string;
- keywords?: readonly string[];
+ description: string;
+ canonicalPath: string;
+ type?: "website" | "article";
imageUrl?: string;
+ imageAlt?: string;
+ publishedTime?: Date;
+ tags?: readonly string[];
+ noindex?: boolean;
};
-const { title, desc, imageUrl, urlPath, keywords = [] } = Astro.props;
-
-const url = `https://asynctalk.com${urlPath}`;
-
-const metaTitle = title + " | AsyncTalk podcast";
+const {
+ title,
+ description,
+ canonicalPath,
+ type = "website",
+ imageUrl,
+ imageAlt = title,
+ publishedTime,
+ tags = [],
+ noindex = false,
+} = Astro.props;
-const logoLink = imageUrl ?? logo.src;
-
-const metaKeywords = ["AsyncTalk", "podcast", "web", "frontend", ...keywords];
+const pageUrl = canonicalUrl(canonicalPath, Astro.site);
+const socialImageUrl = new URL(imageUrl ?? "/og.png", Astro.site).toString();
+const metaTags = compactUrls(tags.map((tag) => tag.trim()));
---
-
-
-
-
-
-
-
-
-
-
{title}
+
+
+{noindex && }
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+{
+ type === "article" && publishedTime && (
+
+ )
+}
+{type === "article" && metaTags.map((tag) => )}
diff --git a/src/constants.ts b/src/constants.ts
index 97c173b..932966d 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -31,6 +31,14 @@ export const socialLinks = [
},
] as const;
+export const podcastLinks = {
+ applePodcasts:
+ "https://podcasts.apple.com/cn/podcast/asynctalk-s01/id1590369272",
+ xiaoyuzhou:
+ "https://www.xiaoyuzhoufm.com/podcast/61684ce4d8fa23fb00fc4d3a",
+ spotify: "https://open.spotify.com/show/6AMzdZxcztIoKlZrGX79lX",
+} as const;
+
export const evonia = {
name: 'Evonia.ai',
url: 'https://evoniaai.github.io/',
diff --git a/src/content.config.ts b/src/content.config.ts
index 8a397e7..138e9f8 100644
--- a/src/content.config.ts
+++ b/src/content.config.ts
@@ -5,9 +5,21 @@ import { z } from 'astro/zod';
const posts = defineCollection({
loader: glob({ pattern: '**/*.mdx', base: './src/content/posts' }),
schema: z.object({
+ type: z.literal('podcast-episode'),
+ slug: z.string().startsWith('/posts/'),
+ guid: z.number().int().nonnegative(),
title: z.string(),
+ subtitle: z.string(),
+ excerpt: z.string().trim().min(40).max(160),
author: z.string(),
publicationDate: z.date(),
+ season: z.number().int().positive(),
+ episodeNumber: z.number().int().nonnegative(),
+ episodeType: z.enum(['full', 'trailer']),
+ url: z.url(),
+ size: z.number().nonnegative(),
+ duration: z.number().nonnegative(),
+ explicit: z.boolean(),
categories: z.array(z.string()),
status: z.enum(['draft', 'pending', 'published']),
xyzLink: z.union([z.url(), z.literal("")]).optional(),
diff --git a/src/content/posts/ep0.mdx b/src/content/posts/ep0.mdx
index aa1a77b..3ba2f51 100644
--- a/src/content/posts/ep0.mdx
+++ b/src/content/posts/ep0.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe, sleaf, Tinko, 小鹿
season: 1
episodeNumber: 0
episodeType: trailer
-excerpt: 在第 0 期我们主要介绍了一下这个节目的背景和目标。
+excerpt: "认识 AsyncTalk:一档面向 Web 开发者的中文播客。主播们介绍节目的创办背景、关注方向,以及‘和我们一起,将 Web 开发带向下一个高度’的长期目标。"
url: https://cdn.asynctalk.com/podcast/episode01.mp3
size: 0
duration: 0
diff --git a/src/content/posts/ep1.mdx b/src/content/posts/ep1.mdx
index d4babff..c06ae91 100644
--- a/src/content/posts/ep1.mdx
+++ b/src/content/posts/ep1.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe, sleaf, Tinko, 小鹿
season: 1
episodeNumber: 1
episodeType: trailer
-excerpt: 学前端的入门问题
+excerpt: "从新手常见困惑出发,讨论前端学习路线、基础能力、框架选择与实践方法,帮助刚进入 Web 开发的朋友建立更清晰的成长方向。"
url: https://cdn.asynctalk.com/podcast/episode01.mp3
size: 0
duration: 0
@@ -73,4 +73,3 @@ categories:
## 后记:
* TS 官方的态度是,学习 TS 之前必须学习 JS: https://www.typescriptlang.org/docs/handbook/typescript-from-scratch.html#learning-javascript-and-typescript
* eval或with等用法需要了解,一般项目里不应该使用,因为会带来额外的风险,但是在一些底层库中为了实现更加灵活的特性是需要使用这些语言特性的。
-
diff --git a/src/content/posts/ep10.mdx b/src/content/posts/ep10.mdx
index 7290f4a..dce7708 100644
--- a/src/content/posts/ep10.mdx
+++ b/src/content/posts/ep10.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe, sleaf, Tinko, 小鹿
season: 1
episodeNumber: 10
episodeType: trailer
-excerpt: 出海,国际化
+excerpt: "邀请李叶一起讨论产品出海与国际化,从语言、文化和本地化工程到团队协作,梳理面向海外用户时容易忽略的实际问题。"
url: https://cdn.asynctalk.com/podcast/episode01.mp3
size: 0
duration: 0
@@ -86,4 +86,4 @@ categories:
* 意大利,挪威,西班牙用 . 做千的分割,而非德国 https://docs.oracle.com/cd/E19253-01/819-0402/overview-48/index.html
-BGM by Otologic
\ No newline at end of file
+BGM by Otologic
diff --git a/src/content/posts/ep11.mdx b/src/content/posts/ep11.mdx
index 955915d..11ec6c4 100644
--- a/src/content/posts/ep11.mdx
+++ b/src/content/posts/ep11.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe, sleaf, Tinko, 小鹿
season: 1
episodeNumber: 11
episodeType: trailer
-excerpt: 与 Event 斗争的血泪史
+excerpt: "从浏览器事件模型讲到实际工程经验,讨论事件传播、监听策略、交互设计与性能优化,复盘开发者与 Event 斗争时常见的坑。"
url: https://cdn.asynctalk.com/podcast/episode01.mp3
size: 0
duration: 0
@@ -79,4 +79,4 @@ categories:
* How Apple could kill CAPTCHAs with Private Access Tokens https://appleinsider.com/articles/22/06/14/how-apple-could-kill-captchas-with-private-access-tokens
-BGM by Otologic
\ No newline at end of file
+BGM by Otologic
diff --git a/src/content/posts/ep12.mdx b/src/content/posts/ep12.mdx
index abba8d3..3b4df2d 100644
--- a/src/content/posts/ep12.mdx
+++ b/src/content/posts/ep12.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe, sleaf, Tinko, 小鹿
season: 1
episodeNumber: 12
episodeType: trailer
-excerpt: 官网与 SEO
+excerpt: "借 AsyncTalk 官网上线的机会系统聊 SEO,涵盖页面结构、元数据、robots.txt、站点地图和搜索收录等前端开发者需要掌握的基础。"
url: https://cdn.asynctalk.com/podcast/episode01.mp3
size: 0
duration: 0
@@ -71,4 +71,4 @@ categories:
- PageRank: [http://ilpubs.stanford.edu:8090/422/1/1999-66.pdf](http://ilpubs.stanford.edu:8090/422/1/1999-66.pdf)
- v2ex - 程序员论坛: [https://v2ex.com/](https://v2ex.com/)
-BGM by Otologic
\ No newline at end of file
+BGM by Otologic
diff --git a/src/content/posts/ep13.mdx b/src/content/posts/ep13.mdx
index 199d077..d5d6c59 100644
--- a/src/content/posts/ep13.mdx
+++ b/src/content/posts/ep13.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe, sleaf, Tinko
season: 2
episodeNumber: 13
episodeType: trailer
-excerpt: 浏览器最近的新功能
+excerpt: "第二季从浏览器新能力开始,盘点近年出现的 Web API,讨论它们能解决什么问题、兼容性如何,以及前端应用可以探索的新边界。"
url: https://cdn.asynctalk.com/podcast/episode01.mp3
size: 0
duration: 0
diff --git a/src/content/posts/ep14.mdx b/src/content/posts/ep14.mdx
index 8e697bc..723b627 100644
--- a/src/content/posts/ep14.mdx
+++ b/src/content/posts/ep14.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe, Tinko, 小鹿
season: 2
episodeNumber: 14
episodeType: trailer
-excerpt: bun 新生煎出炉啦
+excerpt: "追踪新兴 JavaScript Runtime Bun,了解它的一体化工具链、性能卖点和生态现状,并讨论它可能给 Node.js 与前端工程带来的变化。"
url: https://cdn.asynctalk.com/podcast/episode01.mp3
size: 0
duration: 0
diff --git a/src/content/posts/ep15.mdx b/src/content/posts/ep15.mdx
index 5efd621..8fd6d32 100644
--- a/src/content/posts/ep15.mdx
+++ b/src/content/posts/ep15.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe, Sleaf, Tinko, 小鹿
season: 2
episodeNumber: 15
episodeType: trailer
-excerpt: 前端的状态管理方案
+excerpt: "从 window.store、Flux 到双向绑定、单向数据流和类型缓存,回顾前端状态管理演进,并分析各种方案背后的妥协与适用场景。"
url: https://cdn.asynctalk.com/podcast/episode01.mp3
size: 0
duration: 0
diff --git a/src/content/posts/ep16.mdx b/src/content/posts/ep16.mdx
index d71bf60..67f3ec5 100644
--- a/src/content/posts/ep16.mdx
+++ b/src/content/posts/ep16.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe, Sleaf, Tinko, 小鹿
season: 2
episodeNumber: 16
episodeType: trailer
-excerpt: 定宽容器长文本缩放
+excerpt: "从定宽容器中的长文本缩放问题出发,拆解 Canvas 文本测量、字体渲染和布局计算,展示如何把一个 UI 难题转化为可靠实现。"
url: https://cdn.asynctalk.com/podcast/episode01.mp3
size: 0
duration: 0
diff --git a/src/content/posts/ep17.mdx b/src/content/posts/ep17.mdx
index b12ee2e..43d5998 100644
--- a/src/content/posts/ep17.mdx
+++ b/src/content/posts/ep17.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe, Sleaf, Tinko, 小鹿
season: 2
episodeNumber: 17
episodeType: trailer
-excerpt: “差生”文具多 - 开发工具
+excerpt: "主播们分享日常使用的 Terminal、Shell 与命令行工具,聊配置习惯、效率提升和选择开发工具时真正影响体验的细节。"
url: https://cdn.asynctalk.com/podcast/episode01.mp3
size: 0
duration: 0
diff --git a/src/content/posts/ep18.mdx b/src/content/posts/ep18.mdx
index 564ed1b..ea21200 100644
--- a/src/content/posts/ep18.mdx
+++ b/src/content/posts/ep18.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe, Sleaf, Tinko, 小鹿
season: 2
episodeNumber: 18
episodeType: trailer
-excerpt: EP18 “差生”文具多 - 软件
+excerpt: "开发工具系列继续盘点编辑器、插件和常用 App,分享主播们的工作流、使用偏好,以及软件工具怎样帮助开发者减少重复劳动。"
url: https://cdn.asynctalk.com/podcast/episode01.mp3
size: 0
duration: 0
diff --git a/src/content/posts/ep19.mdx b/src/content/posts/ep19.mdx
index 3af5e43..a0329fe 100644
--- a/src/content/posts/ep19.mdx
+++ b/src/content/posts/ep19.mdx
@@ -10,7 +10,7 @@ author: Roc, AnnatarHe, Sleaf, Tinko, 小鹿
season: 2
episodeNumber: 19
episodeType: trailer
-excerpt: EP19 上班也就图一乐,真挣钱还得卖 App (吗?)
+excerpt: "与 Roc 一起讨论 Side Project 和独立开发:为什么开始、如何选择产品、真实收益怎样,以及怎样平衡本职工作、绩效与个人项目。"
url: https://www.xiaoyuzhoufm.com/episode/6363b854ea3c62a2129ddaa9
size: 0
duration: 0
diff --git a/src/content/posts/ep2.mdx b/src/content/posts/ep2.mdx
index 7f1c19e..d5dad0a 100644
--- a/src/content/posts/ep2.mdx
+++ b/src/content/posts/ep2.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe, sleaf, Tinko, 小鹿
season: 1
episodeNumber: 2
episodeType: trailer
-excerpt: 又不是不能用 - 跨平台方案
+excerpt: "从 React Native、Flutter、PWA、Electron 到小程序,多位主播比较主流跨平台方案的开发体验、适用场景与现实取舍。"
url: https://cdn.asynctalk.com/podcast/episode01.mp3
size: 0
duration: 0
@@ -59,4 +59,4 @@ categories:
* Hybrid Mobile App https://zhuanlan.zhihu.com/p/21387961
-BGM by Otologic
\ No newline at end of file
+BGM by Otologic
diff --git a/src/content/posts/ep20.mdx b/src/content/posts/ep20.mdx
index 68d7d4c..c29d7c5 100644
--- a/src/content/posts/ep20.mdx
+++ b/src/content/posts/ep20.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 2
episodeNumber: 20
episodeType: trailer
-excerpt: EP20 TurboPack 卷起来了
+excerpt: "借 Turbopack 发布回顾前端打包工具的演进,比较性能与架构选择,并设想如果从零设计新一代 bundler 应该优先解决什么。"
url: https://www.xiaoyuzhoufm.com/episode/6384e4b916da64dfd52663b6
size: 0
duration: 0
@@ -67,4 +67,3 @@ categories:
BGM by Otologic
-
diff --git a/src/content/posts/ep21.mdx b/src/content/posts/ep21.mdx
index c3a3edc..c0c671a 100644
--- a/src/content/posts/ep21.mdx
+++ b/src/content/posts/ep21.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 2
episodeNumber: 21
episodeType: trailer
-excerpt: EP21 GraphQL —— 一种连首富都搞不定的 “新” 技术
+excerpt: "介绍 GraphQL 的发展、类型系统与查询模式,分析它相对 REST 的优势、复杂度和常见缺点,以及团队落地时可采用的解决方案。"
url: https://www.xiaoyuzhoufm.com/episode/63a1c3e05147c2955ddb9e33
size: 0
duration: 0
@@ -64,4 +64,3 @@ categories:
BGM by Otologic
-
diff --git a/src/content/posts/ep22.mdx b/src/content/posts/ep22.mdx
index c9dfe63..3e226c3 100644
--- a/src/content/posts/ep22.mdx
+++ b/src/content/posts/ep22.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 2
episodeNumber: 22
episodeType: trailer
-excerpt: EP22 Code is cheap, let’s talk
+excerpt: "从软件设计哲学出发,讨论框架和工具为何需要清晰原则,以及性能、体验、可靠性和维护成本之间应当如何做工程取舍。"
url: https://www.xiaoyuzhoufm.com/episode/63ce15bb4879e7008cb7f621
size: 0
duration: 0
diff --git a/src/content/posts/ep23.mdx b/src/content/posts/ep23.mdx
index f415677..fce42cf 100644
--- a/src/content/posts/ep23.mdx
+++ b/src/content/posts/ep23.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 2
episodeNumber: 23
episodeType: trailer
-excerpt: EP23 让拉数据,像呼吸一样自然
+excerpt: "梳理 React 应用请求数据时的缓存、状态和错误处理问题,比较不同解决方案,并重点分析 React Query 的设计思路与核心能力。"
url: https://www.xiaoyuzhoufm.com/episode/63fb88ca682d137141fb9234
size: 0
duration: 0
diff --git a/src/content/posts/ep24.mdx b/src/content/posts/ep24.mdx
index 4d4b066..e05e5e2 100644
--- a/src/content/posts/ep24.mdx
+++ b/src/content/posts/ep24.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 2
episodeNumber: 24
episodeType: trailer
-excerpt: EP24 龙哥锐评 Rspack,开源与新加坡生活
+excerpt: "邀请子龙从 Rspack 聊到前端构建工具,延伸讨论开源协作、ChatGPT 带来的变化,以及开发者在新加坡工作生活的真实体验。"
url: https://www.xiaoyuzhoufm.com/episode/6420640c910c424c75c371a8
size: 0
duration: 0
diff --git a/src/content/posts/ep25.mdx b/src/content/posts/ep25.mdx
index f9f2fb4..dee00d2 100644
--- a/src/content/posts/ep25.mdx
+++ b/src/content/posts/ep25.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 2
episodeNumber: 25
episodeType: trailer
-excerpt: EP25 还有没有新的花活?WASM, web container & NodeBox
+excerpt: "从 WebContainers 与 NodeBox 聊到 WebAssembly 的发展和现状,分析浏览器内运行开发环境的价值、限制,以及 WASM 真正成熟的业务场景。"
url: https://www.xiaoyuzhoufm.com/episode/644f753a306513184cd0b529
size: 0
duration: 0
@@ -74,4 +74,4 @@ Hi, 大家劳动节快乐~
- 事后看了下 web 版的 photoshop,一个 apollo_assets 的 wasm 文件有 58 MB 😂
-BGM by Otologic
\ No newline at end of file
+BGM by Otologic
diff --git a/src/content/posts/ep26.mdx b/src/content/posts/ep26.mdx
index db227b6..e513958 100644
--- a/src/content/posts/ep26.mdx
+++ b/src/content/posts/ep26.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 2
episodeNumber: 26
episodeType: trailer
-excerpt: EP26 那些无疾而终的技术项目们
+excerpt: "从 Next.js App Router 的实际问题出发,复盘那些曾被寄予厚望却逐渐停滞的技术项目,讨论技术选型、维护成本与生态风险。"
url: https://www.xiaoyuzhoufm.com/episode/649330935ac282050e8386fa
size: 0
duration: 0
@@ -80,4 +80,4 @@ categories:
- remax: [https://github.com/remaxjs/remax/issues/1977](https://github.com/remaxjs/remax/issues/1977)
- taro: [https://docs.taro.zone/docs/](https://docs.taro.zone/docs/)
-BGM by Otologic
\ No newline at end of file
+BGM by Otologic
diff --git a/src/content/posts/ep27.mdx b/src/content/posts/ep27.mdx
index f5293c3..84efdfc 100644
--- a/src/content/posts/ep27.mdx
+++ b/src/content/posts/ep27.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 2
episodeNumber: 27
episodeType: trailer
-excerpt: EP27 组件开发和路易十六有什么关系?- Headless Component
+excerpt: "介绍 Headless Component 的设计方式,分析逻辑复用、设计系统集成和可测试性优势,以及额外架构复杂度与思维转换成本。"
url: https://www.xiaoyuzhoufm.com/episode/64c6af739d716f2aa110d1f1
size: 0
duration: 0
@@ -65,4 +65,4 @@ categories:
- HEADLESS USER INTERFACE COMPONENTS:[https://www.merrickchristensen.com/articles/headless-user-interface-components/](https://www.merrickchristensen.com/articles/headless-user-interface-components/)
-BGM by Otologic
\ No newline at end of file
+BGM by Otologic
diff --git a/src/content/posts/ep28.mdx b/src/content/posts/ep28.mdx
index dab1f11..5580244 100644
--- a/src/content/posts/ep28.mdx
+++ b/src/content/posts/ep28.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 2
episodeNumber: 28
episodeType: trailer
-excerpt: EP28 这次咱肯定全栈了 - React Server Component
+excerpt: "围绕 React Server Components 和 Next.js 展开讨论,分析服务器组件如何改变数据获取、渲染边界与前后端协作,以及它的现实争议。"
url: https://www.xiaoyuzhoufm.com/episode/64e7a93231512d361f1fe71d
size: 0
duration: 0
@@ -73,4 +73,4 @@ React Server Component 是最近的大事。本期蹭一次热点,锐评 RSC
- Everything I wish I knew before moving 50,000 lines of code to React Server Components: [https://www.mux.com/blog/what-are-react-server-components](https://www.mux.com/blog/what-are-react-server-components)
- Ruby on Rails: [https://rubyonrails.org/](https://rubyonrails.org/)
-BGM by Otologic
\ No newline at end of file
+BGM by Otologic
diff --git a/src/content/posts/ep29.mdx b/src/content/posts/ep29.mdx
index d2180e4..6cfe811 100644
--- a/src/content/posts/ep29.mdx
+++ b/src/content/posts/ep29.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 2
episodeNumber: 29
episodeType: trailer
-excerpt: EP29 装上 Zod 没有 Bug
+excerpt: "从表单、API 返回值和服务端参数校验切入,介绍 Zod 的运行时类型能力,讨论大型 TypeScript 项目为何仍然需要可靠的数据验证。"
url: https://www.xiaoyuzhoufm.com/episode/6522f56c6e09175708acdaed
size: 0
duration: 0
@@ -90,4 +90,4 @@ categories:
- Zod 对比: [https://zod.dev/?id=comparison](https://zod.dev/?id=comparison)
-BGM by Otologic
\ No newline at end of file
+BGM by Otologic
diff --git a/src/content/posts/ep3.mdx b/src/content/posts/ep3.mdx
index df060dc..ce2f976 100644
--- a/src/content/posts/ep3.mdx
+++ b/src/content/posts/ep3.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe, sleaf, Tinko, 小鹿
season: 1
episodeNumber: 3
episodeType: trailer
-excerpt: 工程师的自我修养 - 前端的工程化
+excerpt: "围绕前端工程化展开讨论:工具链如何演进、团队为何需要工程规范,以及开发者怎样参与构建更高效的下一代前端基础设施。"
url: https://cdn.asynctalk.com/podcast/episode01.mp3
size: 0
duration: 0
@@ -73,4 +73,4 @@ categories:
联系方式:async.talk@gmail.com
-BGM by Otologic
\ No newline at end of file
+BGM by Otologic
diff --git a/src/content/posts/ep30.mdx b/src/content/posts/ep30.mdx
index 39a8b79..fc2b444 100644
--- a/src/content/posts/ep30.mdx
+++ b/src/content/posts/ep30.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 2
episodeNumber: 30
episodeType: trailer
-excerpt: EP30 Server Action 是有点儿搞笑,但前端得学
+excerpt: "围绕 Next.js Server Components 与 Server Actions 分享实践观点,讨论新模型带来的开发体验、边界模糊和前端工程师必须面对的变化。"
url: https://www.xiaoyuzhoufm.com/episode/655057a885c11a0864c93c8b
size: 0
duration: 0
@@ -71,4 +71,4 @@ Server Action 是有点儿搞笑,但前端得学。
- use-shell 在 twitter 上看到的,没能找到当初的链接,请各位自行脑补 😂
-BGM by Otologic
\ No newline at end of file
+BGM by Otologic
diff --git a/src/content/posts/ep31.mdx b/src/content/posts/ep31.mdx
index ef45558..559efa2 100644
--- a/src/content/posts/ep31.mdx
+++ b/src/content/posts/ep31.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 2
episodeNumber: 31
episodeType: trailer
-excerpt: EP31 WebWorker 联动 - OpenAI 和 Github 又有什么新活?
+excerpt: "与 WebWorker 串台讨论 OpenAI、GitHub Copilot、Prompt 和 AI 编程工具,也分享前端开发经历以及生成式 AI 对工作的真实影响。"
url: https://www.xiaoyuzhoufm.com/episode/656ca6896b6a467be78582d0
size: 0
duration: 0
@@ -96,4 +96,4 @@ WebWorker 节目链接: [No.42 和 AsyncTalk 的 AnnatarHe 串台聊 AI Prompt
### 关于「2023 技术播客节」
-缘起于2022仲夏时节技术播客之间的梦幻联动,我们感受到了社区共创共建的力量。今年我们再接再厉,集结了30+播客、5大出品人、20+社区,希望拉动更多技术生态的内容创作者,一起用声音来表达,建设自家技术影响力,推动更高粘性、更深互联、更持久共鸣的用户社区构建。
\ No newline at end of file
+缘起于2022仲夏时节技术播客之间的梦幻联动,我们感受到了社区共创共建的力量。今年我们再接再厉,集结了30+播客、5大出品人、20+社区,希望拉动更多技术生态的内容创作者,一起用声音来表达,建设自家技术影响力,推动更高粘性、更深互联、更持久共鸣的用户社区构建。
diff --git a/src/content/posts/ep32.mdx b/src/content/posts/ep32.mdx
index de8903c..198d5d6 100644
--- a/src/content/posts/ep32.mdx
+++ b/src/content/posts/ep32.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 2
episodeNumber: 32
episodeType: trailer
-excerpt: EP32 npm run
+excerpt: "与 Philo、TK 组成跨国串台,从出国原因、路径和目的地选择聊到海外生活、业务拓展与商业观察,为考虑海外发展的开发者提供参考。"
url: https://www.xiaoyuzhoufm.com/episode/65a18224567153aad51ebe69
size: 0
duration: 0
@@ -101,4 +101,3 @@ GTA: The Greater Toronto Area
- 日本高度人才准证: [https://www.mofa.go.jp/j_info/visit/visa/long/visa16.html](https://www.mofa.go.jp/j_info/visit/visa/long/visa16.html)
- 香港高端人才通行证: [https://www.immd.gov.hk/eng/services/visas/TTPS.html](https://www.immd.gov.hk/eng/services/visas/TTPS.html)
-
diff --git a/src/content/posts/ep33.mdx b/src/content/posts/ep33.mdx
index 9fc8ab1..98c2644 100644
--- a/src/content/posts/ep33.mdx
+++ b/src/content/posts/ep33.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 2
episodeNumber: 33
episodeType: trailer
-excerpt: EP33 这能搞钱吗? —— 从新邮件到开发者服务
+excerpt: "以 React Email 为起点,讨论开发者产品从想法到融资、设计和营销的难点,并分析面向开发者提供工具与服务是否是一片新机会。"
url: https://www.xiaoyuzhoufm.com/episode/65b643e901c425338399697f
size: 0
duration: 0
diff --git a/src/content/posts/ep34.mdx b/src/content/posts/ep34.mdx
index 9ca9183..935eee6 100644
--- a/src/content/posts/ep34.mdx
+++ b/src/content/posts/ep34.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 2
episodeNumber: 34
episodeType: trailer
-excerpt: EP34 JS Runtime 怎么也卷起来了
+excerpt: "Node.js、Deno、Bun、Hermes 和 LLRT 同台竞争,节目比较不同 JavaScript Runtime 的目标、性能、生态与适用场景。"
url: https://www.xiaoyuzhoufm.com/episode/65dcaf690847349e0cae46b6
size: 0
duration: 0
diff --git a/src/content/posts/ep35.mdx b/src/content/posts/ep35.mdx
index fc1f232..ffa82ea 100644
--- a/src/content/posts/ep35.mdx
+++ b/src/content/posts/ep35.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 2
episodeNumber: 35
episodeType: trailer
-excerpt: EP35 让我看看博客和官网应该怎么写
+excerpt: "讨论开发者博客和产品官网应该如何设计与实现,从内容组织、技术栈、部署平台到 SEO,梳理建立长期线上身份的关键选择。"
url: https://www.xiaoyuzhoufm.com/episode/6606e5147417b84f211675ac
size: 0
duration: 0
diff --git a/src/content/posts/ep36.mdx b/src/content/posts/ep36.mdx
index 5c74820..0773300 100644
--- a/src/content/posts/ep36.mdx
+++ b/src/content/posts/ep36.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 2
episodeNumber: 36
episodeType: trailer
-excerpt: EP36 我们总会毕业
+excerpt: "从身边朋友的职业变化谈起,讨论开发者离开岗位、转换方向和面对行业评价时的选择,思考工作之外如何建立更可持续的成长路径。"
url: https://www.xiaoyuzhoufm.com/episode/66255b76200abebe6e54b9c1
size: 0
duration: 0
@@ -89,4 +89,4 @@ AsyncTalk EP36 来谈论最近很沉重的工作话题。
【TODO】
-BGM by Otologic
\ No newline at end of file
+BGM by Otologic
diff --git a/src/content/posts/ep37.mdx b/src/content/posts/ep37.mdx
index 9a63df1..21c7da8 100644
--- a/src/content/posts/ep37.mdx
+++ b/src/content/posts/ep37.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 2
episodeNumber: 37
episodeType: trailer
-excerpt: EP37 在?听说你也在卷编辑器?
+excerpt: "从 Textarea、富文本编辑器到 Headless Framework,回顾文本编辑技术演进,并讨论 AI、协作和 Local First 带来的产品机会。"
url: https://www.xiaoyuzhoufm.com/episode/664d95644efbc0c3dc4372c7
size: 0
duration: 0
@@ -80,4 +80,4 @@ categories:
- Observable Framework 这个产品看起来非常不错,推荐一把: [https://observablehq.com/](https://observablehq.com/)
-BGM by Otologic
\ No newline at end of file
+BGM by Otologic
diff --git a/src/content/posts/ep38.mdx b/src/content/posts/ep38.mdx
index 4123122..98d534b 100644
--- a/src/content/posts/ep38.mdx
+++ b/src/content/posts/ep38.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 2
episodeNumber: 38
episodeType: trailer
-excerpt: EP38 「前端輕鬆聊」联动 - 候选人只要拿到 Offer 就可以了,而面试就要考虑很多了
+excerpt: "与「前端輕鬆聊」Eric 串台,从北美求职环境出发,对比候选人与面试官目标,讨论招聘流程、评价标准和拿到 Offer 背后的现实。"
url: https://www.xiaoyuzhoufm.com/episode/6665d55394977a26ef40e3ec
size: 0
duration: 0
@@ -72,4 +72,4 @@ categories:
--------
-BGM by Otologic
\ No newline at end of file
+BGM by Otologic
diff --git a/src/content/posts/ep39.mdx b/src/content/posts/ep39.mdx
index ddcfdab..c7805f5 100644
--- a/src/content/posts/ep39.mdx
+++ b/src/content/posts/ep39.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 2
episodeNumber: 39
episodeType: trailer
-excerpt: EP39 Server Rendering 升职加薪的好路子
+excerpt: "从前端性能优化聊到 SSR、React Server Components 与 Node.js,分析服务器渲染为何重新成为重要的 Web 架构方向。"
url: https://www.xiaoyuzhoufm.com/episode/668ee88747160d61ff8302f8
size: 0
duration: 0
@@ -67,4 +67,4 @@ categories:
--------
-BGM by Otologic
\ No newline at end of file
+BGM by Otologic
diff --git a/src/content/posts/ep4.mdx b/src/content/posts/ep4.mdx
index 2fb346e..6e79aef 100644
--- a/src/content/posts/ep4.mdx
+++ b/src/content/posts/ep4.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe, sleaf, Tinko, 小鹿
season: 1
episodeNumber: 4
episodeType: trailer
-excerpt: 你看我算全栈吗?- 当开始用 js 写后端
+excerpt: "邀请子亭一起聊 Node.js、JavaScript 后端与全栈开发,讨论前端工程师走向服务端时需要补齐的能力、边界和技术选择。"
url: https://cdn.asynctalk.com/podcast/episode01.mp3
size: 0
duration: 0
@@ -73,4 +73,4 @@ categories:
* Rust Is The Future of JavaScript Infrastructure: https://leerob.io/blog/rust
-BGM by Otologic
\ No newline at end of file
+BGM by Otologic
diff --git a/src/content/posts/ep40.mdx b/src/content/posts/ep40.mdx
index a4ff00f..18355d9 100644
--- a/src/content/posts/ep40.mdx
+++ b/src/content/posts/ep40.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 2
episodeNumber: 40
episodeType: trailer
-excerpt: EP39 状态机自古以来是前端领域不可分割的一部分
+excerpt: "从状态机切入前端架构经验,讨论同步与异步、框架信仰和复杂状态管理,说明为何显式建模状态仍是可靠应用不可缺少的一环。"
url: https://www.xiaoyuzhoufm.com/episode/66b11d78f0b53acaf298119d
size: 0
duration: 0
@@ -65,4 +65,4 @@ categories:
### 📚 扩展阅读
-- Improving State Management with Xstate: Refactoring Authorization Flows: [https://annatarhe.github.io/2024/08/04/improving-state-management-with-xstate-refactoring-authorization-flows.html](https://annatarhe.github.io/2024/08/04/improving-state-management-with-xstate-refactoring-authorization-flows.html)
\ No newline at end of file
+- Improving State Management with Xstate: Refactoring Authorization Flows: [https://annatarhe.github.io/2024/08/04/improving-state-management-with-xstate-refactoring-authorization-flows.html](https://annatarhe.github.io/2024/08/04/improving-state-management-with-xstate-refactoring-authorization-flows.html)
diff --git a/src/content/posts/ep41.mdx b/src/content/posts/ep41.mdx
index 0f7db89..e426615 100644
--- a/src/content/posts/ep41.mdx
+++ b/src/content/posts/ep41.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 2
episodeNumber: 41
episodeType: trailer
-excerpt: EP40 前端的天命就是做表单
+excerpt: "表单依然是前端最常见也最难做好的业务界面。本期讨论表单状态、校验、交互和组件抽象,以及 AI 时代前端工作的真正价值。"
url: https://www.xiaoyuzhoufm.com/episode/6706bc08212cfe2277425b5f
size: 0
duration: 0
@@ -52,4 +52,4 @@ AI 发展很猛,可是我们前端却不怕失业。因为我们有表单可
- Antd.Form: [https://ant.design/components/form-cn](https://ant.design/components/form-cn)
- react-hook-form: [https://react-hook-form.com/](https://react-hook-form.com/)
-BGM by Otologic
\ No newline at end of file
+BGM by Otologic
diff --git a/src/content/posts/ep42.mdx b/src/content/posts/ep42.mdx
index 9970af9..8f5fcb6 100644
--- a/src/content/posts/ep42.mdx
+++ b/src/content/posts/ep42.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 2
episodeNumber: 42
episodeType: trailer
-excerpt: EP42 是升级 Node 还是换用 Deno?
+excerpt: "借 Deno 2 发布回顾 Node.js、io.js 与 Deno 的演进,比较兼容性、工具链和部署体验,讨论升级 Node 还是切换 Runtime。"
url: https://www.xiaoyuzhoufm.com/episode/6713dcba3862a5b5425a4c98
size: 0
duration: 0
@@ -72,4 +72,4 @@ Deno 2 还是很有趣的,各位可以试试看 😁
### 📚 扩展阅读
-- Bash/Sh is an objectively awful programming language: [https://news.ycombinator.com/item?id=35992575](https://news.ycombinator.com/item?id=35992575)
\ No newline at end of file
+- Bash/Sh is an objectively awful programming language: [https://news.ycombinator.com/item?id=35992575](https://news.ycombinator.com/item?id=35992575)
diff --git a/src/content/posts/ep43.mdx b/src/content/posts/ep43.mdx
index 8474ed3..aa45812 100644
--- a/src/content/posts/ep43.mdx
+++ b/src/content/posts/ep43.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 2
episodeNumber: 43
episodeType: trailer
-excerpt: EP43 成人话题 - 大厂生活(生存)指导音频
+excerpt: "从职业与收入聊到个人项目和大厂生活,分享工作协作、绩效和生存经验,也讨论开发者怎样在组织中保持判断与行动空间。"
url: https://www.xiaoyuzhoufm.com/episode/677a61aa996fe41f79325f0f
size: 0
duration: 0
@@ -75,4 +75,4 @@ categories:
- v0: [https://v0.dev/](https://v0.dev/)
- 稚晖君: [https://space.bilibili.com/20259914/](https://space.bilibili.com/20259914/)
- stormzhang: [https://github.com/stormzhang](https://github.com/stormzhang)
-- Evan You: [https://github.com/yyx990803](https://github.com/yyx990803)
\ No newline at end of file
+- Evan You: [https://github.com/yyx990803](https://github.com/yyx990803)
diff --git a/src/content/posts/ep44.mdx b/src/content/posts/ep44.mdx
index 0dad066..eb2bd0e 100644
--- a/src/content/posts/ep44.mdx
+++ b/src/content/posts/ep44.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 2
episodeNumber: 44
episodeType: trailer
-excerpt: EP44 AI 在卷编辑器了
+excerpt: "AI 产品快速涌入代码编辑器,本期讨论编辑器为什么成为生成式 AI 的重要入口、不同产品方向,以及这波竞争会怎样改变开发体验。"
url: https://www.xiaoyuzhoufm.com/episode/67cdab64b47e8edbe9c5ef6d
size: 0
duration: 0
@@ -78,4 +78,4 @@ AI 发展太快了,产品出得也挺给力。那么 AI 产品有什么方向
- Introducing the Model Context Protocol: [https://www.anthropic.com/news/model-context-protocol](https://www.anthropic.com/news/model-context-protocol)
- Building effective agents: [https://www.anthropic.com/engineering/building-effective-agents](https://www.anthropic.com/engineering/building-effective-agents)
- Anthropic thinks you should build agents like this: [https://www.aihero.dev/building-effective-agents](https://www.aihero.dev/building-effective-agents)
-- 花了大半个月,我终于逆向分析了Github Copilot: [https://github.com/mengjian-github/copilot-analysis](https://github.com/mengjian-github/copilot-analysis)
\ No newline at end of file
+- 花了大半个月,我终于逆向分析了Github Copilot: [https://github.com/mengjian-github/copilot-analysis](https://github.com/mengjian-github/copilot-analysis)
diff --git a/src/content/posts/ep45.mdx b/src/content/posts/ep45.mdx
index 18a235e..d6c71f6 100644
--- a/src/content/posts/ep45.mdx
+++ b/src/content/posts/ep45.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 2
episodeNumber: 45
episodeType: trailer
-excerpt: EP45 和 Anthony Fu 讨论 Vite, Vitest, Vue, 法国经历。以及 React 大战
+excerpt: "邀请 Anthony Fu 聊 Vite、Vitest、Vue 与开源工作,讨论大模型对框架生态的影响、法国生活经历,以及 React 和 Vue 的理念碰撞。"
url: https://www.xiaoyuzhoufm.com/episode/6829b32e79771f84133a7e25
size: 0
duration: 0
@@ -80,4 +80,4 @@ categories:
### 📚 扩展阅读
-- The /llms.txt file: [https://llmstxt.org/](https://llmstxt.org/)
\ No newline at end of file
+- The /llms.txt file: [https://llmstxt.org/](https://llmstxt.org/)
diff --git a/src/content/posts/ep46.mdx b/src/content/posts/ep46.mdx
index 2382fff..57c2276 100644
--- a/src/content/posts/ep46.mdx
+++ b/src/content/posts/ep46.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 2
episodeNumber: 46
episodeType: trailer
-excerpt: EP45 如何正确开喷 Next.js
+excerpt: "系统梳理 Next.js 值得批评的地方,从 App Router、缓存和部署复杂度到框架边界,讨论如何基于事实评价而不是跟风开喷。"
url: https://www.xiaoyuzhoufm.com/episode/68505650cdecf72d4cf77a2b
size: 0
duration: 0
@@ -66,4 +66,4 @@ categories:
- Node.js Heap profiler: [https://nodejs.org/api/inspector.html#heap-profiler](https://nodejs.org/api/inspector.html#heap-profiler)
- use cache: [https://nextjs.org/docs/app/api-reference/directives/use-cache](https://nextjs.org/docs/app/api-reference/directives/use-cache)
- next.js fetch [https://nextjs.org/docs/app/api-reference/functions/fetch](https://nextjs.org/docs/app/api-reference/functions/fetch)
-- The select element can now be customized with CSS [https://developer.chrome.com/blog/a-customizable-select](https://developer.chrome.com/blog/a-customizable-select)
\ No newline at end of file
+- The select element can now be customized with CSS [https://developer.chrome.com/blog/a-customizable-select](https://developer.chrome.com/blog/a-customizable-select)
diff --git a/src/content/posts/ep47.mdx b/src/content/posts/ep47.mdx
index aaa6a70..2241940 100644
--- a/src/content/posts/ep47.mdx
+++ b/src/content/posts/ep47.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 2
episodeNumber: 47
episodeType: trailer
-excerpt: EP47 Claude Code 它不一样
+excerpt: "Claude Code 将编程带入 Agentic 时代。本期分析它与传统 AI 编辑器的差异、工具调用和工作流,以及它对软件开发方式的影响。"
url: https://www.xiaoyuzhoufm.com/episode/68669a1254a5087a9ae34562
size: 0
duration: 0
@@ -61,4 +61,4 @@ Claude Code 的出现同时也将世界带向了下一个 Agentic 编程的时
- FLUX.1 Kontext by black forest labs: [https://bfl.ai/models/flux-kontext](https://bfl.ai/models/flux-kontext)
- Cluade Code: [https://docs.anthropic.com/en/docs/claude-code/overview](https://docs.anthropic.com/en/docs/claude-code/overview)
- ccusage: [https://ccusage.com/](https://ccusage.com/)
-- EP43 成人话题 - 大厂生活(生存)指导音频 [https://asynctalk.com/posts/ep43](https://asynctalk.com/posts/ep43)
\ No newline at end of file
+- EP43 成人话题 - 大厂生活(生存)指导音频 [https://asynctalk.com/posts/ep43](https://asynctalk.com/posts/ep43)
diff --git a/src/content/posts/ep48.mdx b/src/content/posts/ep48.mdx
index 819f537..b3da58d 100644
--- a/src/content/posts/ep48.mdx
+++ b/src/content/posts/ep48.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 2
episodeNumber: 48
episodeType: full
-excerpt: EP48 AI 来了,代码该这么写才能从从容容游刃有余
+excerpt: "AI 辅助编程成为日常后,代码该怎样组织才更容易理解和修改?本期讨论结构、上下文、测试与协作,让人与 Agent 都能高效工作。"
url: https://www.xiaoyuzhoufm.com/episode/68f66104c4a8670d2edc9824
size: 0
duration: 0
diff --git a/src/content/posts/ep49.mdx b/src/content/posts/ep49.mdx
index 11d86bd..0f80339 100644
--- a/src/content/posts/ep49.mdx
+++ b/src/content/posts/ep49.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 3
episodeNumber: 49
episodeType: full
-excerpt: EP49 AsyncTalk 开播啦
+excerpt: "AsyncTalk 正式开启视频播客,介绍接下来关于前沿 Web 开发技术与 AI 探索的内容方向,以及可以持续关注节目的平台与方式。"
url: https://www.xiaoyuzhoufm.com/episode/697ad354903a4fab4e9dc4b2
size: 0
duration: 0
@@ -33,4 +33,3 @@ AsyncTalk 的视频播客节目正式开播啦~
一起来关注吧开播啦
-
diff --git a/src/content/posts/ep5.mdx b/src/content/posts/ep5.mdx
index a13ea39..7be6802 100644
--- a/src/content/posts/ep5.mdx
+++ b/src/content/posts/ep5.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe, sleaf, Tinko, 小鹿
season: 1
episodeNumber: 5
episodeType: trailer
-excerpt: 比黑洞更深的是什么 - 关于 JS 的包管理
+excerpt: "回顾 JavaScript 包管理的发展与复杂性,聊版本控制、依赖解析、npm 发包流程,以及开发者维护和消费软件包时容易踩到的坑。"
url: https://cdn.asynctalk.com/podcast/episode01.mp3
size: 0
duration: 0
@@ -78,4 +78,4 @@ categories:
* lerna 放弃维护:https://github.com/lerna/lerna/issues/2703#issuecomment-777089520
-BGM by Otologic
\ No newline at end of file
+BGM by Otologic
diff --git a/src/content/posts/ep50.mdx b/src/content/posts/ep50.mdx
index e0c8df8..1d47a70 100644
--- a/src/content/posts/ep50.mdx
+++ b/src/content/posts/ep50.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 3
episodeNumber: 50
episodeType: full
-excerpt: EP50 Claude Code 夯爆了
+excerpt: "继续深入 Claude Code,分享它在真实项目中的强大能力、适合交给 Agent 的任务,以及开发者如何提供上下文并建立更可靠的人机协作流程。"
url: https://www.xiaoyuzhoufm.com/episode/697adad22860092c78d342aa
size: 0
duration: 0
diff --git a/src/content/posts/ep51.mdx b/src/content/posts/ep51.mdx
index dd378ca..8fe9d4f 100644
--- a/src/content/posts/ep51.mdx
+++ b/src/content/posts/ep51.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 3
episodeNumber: 51
episodeType: full
-excerpt: EP51 OpenClaw 会是下一个时代吗
+excerpt: "OpenClaw 的爆火推动 AI 产品走向本地化和无托管,也暴露新的安全风险。本期讨论开放 Agent 平台的机会、边界与潜在代价。"
url: https://www.xiaoyuzhoufm.com/episode/6982bc1b1c339447ac91902d
size: 0
duration: 0
diff --git a/src/content/posts/ep52.mdx b/src/content/posts/ep52.mdx
index 43f79d5..879701c 100644
--- a/src/content/posts/ep52.mdx
+++ b/src/content/posts/ep52.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 3
episodeNumber: 52
episodeType: full
-excerpt: EP52 Opus 4.6 很强,那它能干掉 SaaS 吗?
+excerpt: "从 Opus 4.6 的百万上下文、Agent Teams 和推理参数出发,讨论更强模型能否真正冲击专业软件开发与成熟 SaaS 产品。"
url: https://www.xiaoyuzhoufm.com/episode/698bf7ffe33e1b1e6c03b51c
size: 0
duration: 0
diff --git a/src/content/posts/ep53.mdx b/src/content/posts/ep53.mdx
index a5ddc8c..0e83282 100644
--- a/src/content/posts/ep53.mdx
+++ b/src/content/posts/ep53.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 3
episodeNumber: 53
episodeType: full
-excerpt: EP53 TUI 是老登技术吗?
+excerpt: "Claude Code 让终端用户界面重新进入主流视野。本期回顾 TUI 的优势、限制和交互特征,讨论它究竟是过时技术还是 AI 时代的新入口。"
url: https://www.xiaoyuzhoufm.com/episode/698e99d3e33e1b1e6c2f8a76
size: 0
duration: 0
diff --git a/src/content/posts/ep54.mdx b/src/content/posts/ep54.mdx
index cbaef4d..fd14e0e 100644
--- a/src/content/posts/ep54.mdx
+++ b/src/content/posts/ep54.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 3
episodeNumber: 54
episodeType: full
-excerpt: EP54 Remotion 让视频制作进入程序时代
+excerpt: "介绍如何使用 Remotion 和 React 以代码生成宣传视频,并结合 AI 自动化脚本、素材与渲染流程,让视频制作更可复用、更高效。"
url: https://www.xiaoyuzhoufm.com/episodes/699dc29d0362a96f1dd99495
size: 0
duration: 0
diff --git a/src/content/posts/ep55.mdx b/src/content/posts/ep55.mdx
index 1903872..422b7dd 100644
--- a/src/content/posts/ep55.mdx
+++ b/src/content/posts/ep55.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 3
episodeNumber: 55
episodeType: full
-excerpt: EP55 丢掉大脑才能写好代码
+excerpt: "从 Next.js、Tailwind CSS 和 Node.js 的项目实践出发,讨论如何减少无效技术选择,把注意力放回交付、约束与可维护的默认方案。"
url: https://www.xiaoyuzhoufm.com/episode/69a17e7743c8f0e4452747f2
size: 0
duration: 0
diff --git a/src/content/posts/ep56.mdx b/src/content/posts/ep56.mdx
index 4bd259a..99bfe30 100644
--- a/src/content/posts/ep56.mdx
+++ b/src/content/posts/ep56.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 3
episodeNumber: 56
episodeType: full
-excerpt: EP56 急急急 useLinkStatus
+excerpt: "Next.js 页面跳转没有反馈,会让网站显得很慢。本期用 useLinkStatus 改善加载状态和交互响应,拆解一个小 API 带来的体验提升。"
url: https://www.xiaoyuzhoufm.com/episode/69ad9abbdc0cc48a7650214b
size: 0
duration: 0
diff --git a/src/content/posts/ep57.mdx b/src/content/posts/ep57.mdx
index 1c82edb..1941658 100644
--- a/src/content/posts/ep57.mdx
+++ b/src/content/posts/ep57.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 3
episodeNumber: 57
episodeType: full
-excerpt: EP57 Cursor 别联系我,我怕 Claude/Codex 误会
+excerpt: "从 Cursor 的 AI IDE 模式转向 Claude Desktop 与 Codex App,讨论不直接打开代码的 Agent 工作流为何可能代表软件开发的下一阶段。"
url: https://www.xiaoyuzhoufm.com/episode/69b02ef49e77d8f89f61d0de
size: 0
duration: 0
diff --git a/src/content/posts/ep58.mdx b/src/content/posts/ep58.mdx
index 2b5ede4..4553694 100644
--- a/src/content/posts/ep58.mdx
+++ b/src/content/posts/ep58.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 3
episodeNumber: 58
episodeType: full
-excerpt: EP58 Ghostty - 你的 Terminal 还没换新吗?
+excerpt: "体验高颜值终端 Ghostty,从动画、命令提示和渲染表现聊到 libghostty,看看现代 Terminal 怎样兼顾速度、设计与可扩展性。"
url: https://www.xiaoyuzhoufm.com/episode/69d2690b24a6ea4a5475ab8d
size: 0
duration: 0
diff --git a/src/content/posts/ep59.mdx b/src/content/posts/ep59.mdx
index bde63eb..4a9d787 100644
--- a/src/content/posts/ep59.mdx
+++ b/src/content/posts/ep59.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 3
episodeNumber: 59
episodeType: full
-excerpt: EP59 比 Node 快 3 倍的 Bun 能替 Node 打工吗?
+excerpt: "Bun 发布多年并被 Anthropic 收购后,是否已经能替代 Node.js?本期比较它的性能、兼容性、工具链和生产环境成熟度。"
url: https://www.xiaoyuzhoufm.com/episode/69e24038f6e591a7ac739d8a
size: 0
duration: 0
diff --git a/src/content/posts/ep6.mdx b/src/content/posts/ep6.mdx
index 191cb9f..cc86be7 100644
--- a/src/content/posts/ep6.mdx
+++ b/src/content/posts/ep6.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe, sleaf, Tinko, 小鹿
season: 1
episodeNumber: 6
episodeType: trailer
-excerpt: Low Code 到底 Low 不 Low
+excerpt: "结合真实经历讨论低代码平台的应用场景、实现方式与局限,并展望一套真正有用的 Low Code 产品应该解决哪些工程问题。"
url: https://cdn.asynctalk.com/podcast/episode01.mp3
size: 0
duration: 0
@@ -73,4 +73,4 @@ categories:
* “行业毒瘤”低代码: https://www.huxiu.com/article/424995.html
* github copilot: https://copilot.github.com/
-BGM by Otologic
\ No newline at end of file
+BGM by Otologic
diff --git a/src/content/posts/ep60.mdx b/src/content/posts/ep60.mdx
index 3016feb..efc9d19 100644
--- a/src/content/posts/ep60.mdx
+++ b/src/content/posts/ep60.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 3
episodeNumber: 60
episodeType: full
-excerpt: EP60 我把 on-call 甩给了 claude code
+excerpt: "为了避免 on-call 打断睡眠,主播搭建一套极简 harness 让 Claude Code 参与值班,分享告警分析、自动处理和安全边界的实践。"
url: https://www.xiaoyuzhoufm.com/episode/69e4dd58f6e591a7ac26c85e
size: 0
duration: 0
diff --git a/src/content/posts/ep61.mdx b/src/content/posts/ep61.mdx
index dee50f8..74ae968 100644
--- a/src/content/posts/ep61.mdx
+++ b/src/content/posts/ep61.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 3
episodeNumber: 61
episodeType: full
-excerpt: EP61 打日志的两个神级技巧|5 分钟拿捏
+excerpt: "用 JSON 结构化日志让机器快速检索,再用 Wide Events 为每次请求记录完整上下文,两个技巧显著提升线上问题排查效率。"
url: https://www.xiaoyuzhoufm.com/episode/69ee0384ba07781ad1a2f863
size: 0
duration: 0
diff --git a/src/content/posts/ep62.mdx b/src/content/posts/ep62.mdx
index 908ae6b..c3d2656 100644
--- a/src/content/posts/ep62.mdx
+++ b/src/content/posts/ep62.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 3
episodeNumber: 62
episodeType: full
-excerpt: "EP62 聊聊 Prompt: 少点套路,多点效率"
+excerpt: "Prompt 不应只追求控制模型。本期讨论怎样提供目标、上下文与反馈,更自然高效地和 AI 沟通,减少套路化模板带来的额外负担。"
url: https://www.xiaoyuzhoufm.com/episode/69f4c2309b598c69178c8270
size: 0
duration: 0
diff --git a/src/content/posts/ep63.mdx b/src/content/posts/ep63.mdx
index baf6ada..0a2ce53 100644
--- a/src/content/posts/ep63.mdx
+++ b/src/content/posts/ep63.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 3
episodeNumber: 63
episodeType: full
-excerpt: "EP63 祖传 ESLint 该换了:oxlint 是真的快"
+excerpt: "对比 ESLint 与 VoidZero 团队的 oxlint、oxfmt,从真实项目耗时、规则兼容和迁移成本判断 Rust 工具链是否值得替换祖传配置。"
url: https://www.xiaoyuzhoufm.com/episode/69f9e87e5d23820bacb3e909
size: 0
duration: 0
diff --git a/src/content/posts/ep64.mdx b/src/content/posts/ep64.mdx
index e539a86..e223772 100644
--- a/src/content/posts/ep64.mdx
+++ b/src/content/posts/ep64.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 3
episodeNumber: 64
episodeType: full
-excerpt: "EP64 用了 10 年 AntD 之后,聊聊 UI 库的下一个十年"
+excerpt: "从 Ant Design 的高效交付到 shadcn/ui 的源码所有权,比较两类 UI 库的设计哲学,并讨论 RSC 与 AI 时代组件体系的下一步。"
url: https://www.xiaoyuzhoufm.com/episode/6a070f7da8e4f8939f149604
size: 0
duration: 0
diff --git a/src/content/posts/ep65.mdx b/src/content/posts/ep65.mdx
index cf1c55d..3dc9244 100644
--- a/src/content/posts/ep65.mdx
+++ b/src/content/posts/ep65.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 3
episodeNumber: 65
episodeType: full
-excerpt: "EP65 Markdown 没凉,但 HTML 要上桌:AI 输出格式的一场小进化"
+excerpt: "从 Anthropic 关于 HTML 的观点出发,比较 Markdown 与 HTML 作为大模型输出格式的表达力、可交互性和安全成本,观察 AI UI 的新变化。"
url: https://www.xiaoyuzhoufm.com/episode/6a0c5152e443f6948c8d4554
size: 0
duration: 0
diff --git a/src/content/posts/ep66.mdx b/src/content/posts/ep66.mdx
index 65455d5..bd49d00 100644
--- a/src/content/posts/ep66.mdx
+++ b/src/content/posts/ep66.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 3
episodeNumber: 66
episodeType: full
-excerpt: "EP66 用 TS 写命令行工具,比你想的简单多了:Bun 打包成单文件 binary,goreleaser 一把梭搞定签名、公证、分发"
+excerpt: "不用 C、Go 或 Rust,也能用 TypeScript 与 Bun 构建 CLI。本期演示单文件打包,并用 GoReleaser 完成签名、公证和多平台分发。"
url: https://www.xiaoyuzhoufm.com/episode/6a1eb84b92551efcff54fc0f
size: 0
duration: 0
diff --git a/src/content/posts/ep67.mdx b/src/content/posts/ep67.mdx
index e9c7ee4..59d42e3 100644
--- a/src/content/posts/ep67.mdx
+++ b/src/content/posts/ep67.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 3
episodeNumber: 67
episodeType: full
-excerpt: "EP67 写完代码到上线,中间到底发生了啥:GitHub Actions + Claude 代码审查 + 自动发布,从 push 到上线全流程拆给你看"
+excerpt: "打开真实 GitHub Actions 流水线,从 git push、Claude 代码审查到构建和自动发布,完整拆解代码进入生产环境之前发生的事情。"
url: https://www.xiaoyuzhoufm.com/episode/6a2945a00289864adc578925
size: 0
duration: 0
diff --git a/src/content/posts/ep68.mdx b/src/content/posts/ep68.mdx
index dfc7cb8..6412264 100644
--- a/src/content/posts/ep68.mdx
+++ b/src/content/posts/ep68.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 3
episodeNumber: 68
episodeType: full
-excerpt: "EP68 前端代码也能「预制」了:Swagger + Hey API 自动生成请求代码,告别手写 interface"
+excerpt: "针对 REST 接口缺少强类型带来的联调成本,使用 Swagger 与 Hey API 自动生成请求代码和类型,减少手写 interface 与字段不一致。"
url: https://www.xiaoyuzhoufm.com/episode/6a33665acc736a4409ef201d
size: 0
duration: 0
diff --git a/src/content/posts/ep69.mdx b/src/content/posts/ep69.mdx
index d58dda2..feeea01 100644
--- a/src/content/posts/ep69.mdx
+++ b/src/content/posts/ep69.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 3
episodeNumber: 69
episodeType: full
-excerpt: "EP69 GraphQL:治好前后端联调内耗:强类型 schema 统一前后端协作,也聊 n+1、缓存和什么项目不该上 GraphQL"
+excerpt: "用 GraphQL 强类型 Schema 统一前后端协作,减少字段理解偏差;同时分析 N+1、缓存等成本,以及哪些项目并不适合引入 GraphQL。"
url: https://www.xiaoyuzhoufm.com/episode/6a3bab23ffcf45052c119264
size: 0
duration: 0
diff --git a/src/content/posts/ep7.mdx b/src/content/posts/ep7.mdx
index c395eec..aa24ba0 100644
--- a/src/content/posts/ep7.mdx
+++ b/src/content/posts/ep7.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe, sleaf, Tinko, 小鹿
season: 1
episodeNumber: 7
episodeType: trailer
-excerpt: 金三银四,来场面试(上)
+excerpt: "从面试官和候选人的双重视角聊技术面试:如何准备、怎样提问、用什么态度沟通,以及怎样在有限时间内判断彼此是否合适。"
url: https://cdn.asynctalk.com/podcast/episode01.mp3
size: 0
duration: 0
@@ -88,4 +88,4 @@ categories:
* RSA: https://en.wikipedia.org/wiki/RSA_(cryptosystem)
-BGM by Otologic
\ No newline at end of file
+BGM by Otologic
diff --git a/src/content/posts/ep70.mdx b/src/content/posts/ep70.mdx
index 6baf545..a3081d3 100644
--- a/src/content/posts/ep70.mdx
+++ b/src/content/posts/ep70.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 3
episodeNumber: 70
episodeType: full
-excerpt: "EP70 react-call:弹框还能 await:用 createCallable 把自定义组件变成可以 await 的函数,告别手写 Portal 管理弹框"
+excerpt: "用 react-call 的 createCallable 把确认框等自定义组件变成可 await 的函数,简化调用流程,并告别重复编写 Portal 与状态管理代码。"
url: https://www.xiaoyuzhoufm.com/episode/6a478bb0185ee660ea89d2fd
size: 0
duration: 0
diff --git a/src/content/posts/ep71.mdx b/src/content/posts/ep71.mdx
index 312e5f5..47875e7 100644
--- a/src/content/posts/ep71.mdx
+++ b/src/content/posts/ep71.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 3
episodeNumber: 71
episodeType: full
-excerpt: "EP71 AI Agent 必备的 ChatSDK:用统一的对话集成层连接多个聊天平台,让 AI Agent 一次开发、处处运行"
+excerpt: "用 ChatSDK 统一 Telegram、Slack、Discord 等平台的事件、消息和鉴权接口,让同一套 AI Agent 对话逻辑一次开发、跨平台运行。"
url: https://www.xiaoyuzhoufm.com/episode/6a58ae03dad7474f40ea509f
size: 0
duration: 0
diff --git a/src/content/posts/ep72.mdx b/src/content/posts/ep72.mdx
index 01c108a..384eaf9 100644
--- a/src/content/posts/ep72.mdx
+++ b/src/content/posts/ep72.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 3
episodeNumber: 72
episodeType: full
-excerpt: "EP72 YoooClaw:给 AI 一具身体——从通知总结、会议记录到个人 Context,聊聊软硬件结合的 AI Agent"
+excerpt: "体验软硬件结合的 YoooClaw:通过语音驱动 OpenClaw、总结通知和会议,并讨论个人 Context、硬件入口、产品闭环与隐私风险。"
url: https://youtu.be/7mbOHd_EWZw
size: 0
duration: 0
diff --git a/src/content/posts/ep8.mdx b/src/content/posts/ep8.mdx
index c7865c3..092bb6c 100644
--- a/src/content/posts/ep8.mdx
+++ b/src/content/posts/ep8.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe, sleaf, Tinko, 小鹿
season: 1
episodeNumber: 8
episodeType: trailer
-excerpt: 金三银四,来场面试(下)
+excerpt: "技术面试讨论下篇,继续拆解候选人表现、面试评价与招聘决策,并延伸到薪酬沟通、职业选择和面试后的复盘方法。"
url: https://cdn.asynctalk.com/podcast/episode01.mp3
size: 0
duration: 0
@@ -88,4 +88,4 @@ categories:
* RSA: https://en.wikipedia.org/wiki/RSA_(cryptosystem)
-BGM by Otologic
\ No newline at end of file
+BGM by Otologic
diff --git a/src/content/posts/ep9.mdx b/src/content/posts/ep9.mdx
index 1ef99cd..a56b4fd 100644
--- a/src/content/posts/ep9.mdx
+++ b/src/content/posts/ep9.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe, sleaf, Tinko, 小鹿
season: 1
episodeNumber: 9
episodeType: trailer
-excerpt: 大哥大嫂过年好,一起聊聊样式表
+excerpt: "新年回归聊 CSS:介绍不同样式工程思想及其代表方案,比较可维护性、组件化和开发体验,寻找更适合现代前端项目的实践。"
url: https://cdn.asynctalk.com/podcast/episode01.mp3
size: 0
duration: 0
@@ -68,4 +68,4 @@ categories:
* postcss-rs: https://github.com/postcss-rs/postcss-rs
* parcel-css: https://github.com/parcel-bundler/parcel-css
-BGM by Otologic
\ No newline at end of file
+BGM by Otologic
diff --git a/src/content/posts/sp02.mdx b/src/content/posts/sp02.mdx
index 80a7934..b8d3ff4 100644
--- a/src/content/posts/sp02.mdx
+++ b/src/content/posts/sp02.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe, sleaf, Tinko, 小鹿
season: 1
episodeNumber: 13
episodeType: trailer
-excerpt: 第一季结束啦
+excerpt: "AsyncTalk 第一季收官特别节目,回顾这一季的制作与更新,并介绍第二季的回归时间和后续方向,感谢听众一路以来的关注与反馈。"
url: https://cdn.asynctalk.com/podcast/episode01.mp3
size: 0
duration: 0
diff --git a/src/content/posts/sp03.mdx b/src/content/posts/sp03.mdx
index 530af9c..092be9f 100644
--- a/src/content/posts/sp03.mdx
+++ b/src/content/posts/sp03.mdx
@@ -10,7 +10,7 @@ author: AnnatarHe
season: 2
episodeNumber: 32
episodeType: trailer
-excerpt: SP03 杭州上海见
+excerpt: "AsyncTalk 杭州、上海线下见面特别节目,介绍两地轻松聊天活动的安排,邀请听众与主播面对面交流 Web 开发、工作和生活。"
url: https://www.xiaoyuzhoufm.com/episode/65824f58477beea56278903c
size: 0
duration: 0
@@ -36,4 +36,4 @@ AsyncTalk 将在杭州和上海举行一场线下聊天(其实就是找个星
有意参与者发邮件到 [async.talk@gmail.com](mailto:async.talk@gmail.com) 即可
> 我们一定能复活,一定能彼此相见,高高兴兴、快快活活地互相讲述经过的事情
-> 卡拉马佐夫兄弟 陀思妥耶夫斯基
\ No newline at end of file
+> 卡拉马佐夫兄弟 陀思妥耶夫斯基
diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro
index 3868616..00da869 100644
--- a/src/layouts/Layout.astro
+++ b/src/layouts/Layout.astro
@@ -1,21 +1,15 @@
---
import "../global.css";
-interface Props {
- title: string;
-}
import Layout2 from "../components/layout.astro";
import WebMCP from "../components/WebMCP.astro";
-
-const { title } = Astro.props;
---
-
+
-
-
+
@@ -26,7 +20,7 @@ const { title } = Astro.props;
rel="alternate"
type="application/rss+xml"
title="AsyncTalk Podcast - RSS Feed"
- href={`${Astro.site}rss.xml`}
+ href={new URL("/rss.xml", Astro.site)}
/>
- {title}
diff --git a/src/lib/seo.ts b/src/lib/seo.ts
new file mode 100644
index 0000000..a4be9c8
--- /dev/null
+++ b/src/lib/seo.ts
@@ -0,0 +1,58 @@
+export const SITE_URL = "https://asynctalk.com/";
+export const SITE_NAME = "AsyncTalk";
+export const SITE_LANGUAGE = "zh-CN";
+export const OG_LOCALE = "zh_CN";
+
+export function normalizePagePath(pathname: string) {
+ const path = pathname.startsWith("/") ? pathname : `/${pathname}`;
+
+ if (path === "/" || path.endsWith("/")) {
+ return path;
+ }
+
+ const lastSegment = path.split("/").at(-1) ?? "";
+ return lastSegment.includes(".") ? path : `${path}/`;
+}
+
+export function canonicalUrl(pathname: string, site: URL | undefined) {
+ return new URL(normalizePagePath(pathname), site ?? SITE_URL).toString();
+}
+
+export function episodePath(id: string) {
+ const episodeId = id.replace(/^\/?posts\//, "").replace(/\.mdx$/, "");
+ return `/posts/${episodeId}/`;
+}
+
+export function parseAuthors(author: string) {
+ return author
+ .split(",")
+ .map((name) => name.trim())
+ .filter(Boolean)
+ .map((name) => ({ "@type": "Person", name }));
+}
+
+export function jsonLd(value: unknown) {
+ return JSON.stringify(value).replace(/) {
+ return [...new Set(urls.filter((url): url is string => Boolean(url)))];
+}
diff --git a/src/pages/index-legacy.astro b/src/pages/index-legacy.astro
deleted file mode 100644
index fb62628..0000000
--- a/src/pages/index-legacy.astro
+++ /dev/null
@@ -1,123 +0,0 @@
----
-import Layout from '../layouts/Layout.astro';
-import Card from '../components/Card.astro';
----
-
-
-
-
- Welcome to Astro
-
- To get started, open the directory src/pages in your project.
- Code Challenge: Tweak the "Welcome to Astro" message above.
-
-
-
-
-
-
diff --git a/src/pages/index.astro b/src/pages/index.astro
index af1a60b..ffb3d74 100644
--- a/src/pages/index.astro
+++ b/src/pages/index.astro
@@ -3,54 +3,100 @@ import BgImg from "../images/bg-fm.png";
import PageLayout from "../layouts/Layout.astro";
import SEOTags from "../components/seo/seo-tags.astro";
import TitleImage from "../images/title.png";
+import { podcastLinks, socialLinks } from "../constants";
+import { SITE_URL, compactUrls, jsonLd } from "../lib/seo";
-const site = Astro.site;
+const title = "AsyncTalk|和我们一起,将 Web 开发带向下一个高度";
+const description =
+ "AsyncTalk 是一档关注 Web 开发、前端工程化与 AI 的中文播客,分享前沿技术、工程实践与开发者观点。和我们一起,将 Web 开发带向下一个高度。";
+const homeUrl = new URL("/", Astro.site ?? SITE_URL).toString();
+const organizationId = `${homeUrl}#organization`;
+const podcastId = `${homeUrl}#podcast`;
+const logoUrl = new URL("/android-chrome-512x512.png", homeUrl).toString();
+const sameAs = compactUrls([
+ ...Object.values(podcastLinks),
+ ...socialLinks.map((link) => link.url),
+]);
---
-
+
-
-
+

-
- 把前端带向下一个高度
-
+
+ 和我们一起,将 Web 开发带向下一个高度
+
+
+ 关注 Web 开发、前端工程化与 AI,分享前沿技术、工程实践与开发者观点。
+
-
+
diff --git a/src/pages/og-preview.astro b/src/pages/og-preview.astro
deleted file mode 100644
index 242207f..0000000
--- a/src/pages/og-preview.astro
+++ /dev/null
@@ -1,14 +0,0 @@
----
-import Og from "../components/OpenGraph/OG";
----
-
-
diff --git a/src/pages/og.png.ts b/src/pages/og.png.ts
new file mode 100644
index 0000000..0d4cafd
--- /dev/null
+++ b/src/pages/og.png.ts
@@ -0,0 +1,16 @@
+import type { APIRoute } from "astro";
+import OG from "../components/OpenGraph/OG";
+import { PNG } from "../components/OpenGraph/createImage";
+
+export const GET: APIRoute = async function get() {
+ const png = await PNG(
+ OG({ title: "AsyncTalk|和我们一起,将 Web 开发带向下一个高度" }),
+ );
+
+ return new Response(png as unknown as ReadableStream, {
+ headers: {
+ "Content-Type": "image/png",
+ "Cache-Control": "public, max-age=86400, s-maxage=604800",
+ },
+ });
+};
diff --git a/src/pages/posts.astro b/src/pages/posts.astro
index d7413cc..456b658 100644
--- a/src/pages/posts.astro
+++ b/src/pages/posts.astro
@@ -2,37 +2,64 @@
import { getCollection } from "astro:content";
import PageLayout from "../layouts/Layout.astro";
import SEOTags from "../components/seo/seo-tags.astro";
+import { episodePath } from "../lib/seo";
-const posts = await getCollection("posts");
+const posts = await getCollection(
+ "posts",
+ ({ data }) => !import.meta.env.PROD || data.status === "published",
+);
-posts.sort((a, b) => {
- return (
- new Date(b.data.publicationDate).valueOf() -
- new Date(a.data.publicationDate).valueOf()
- );
+posts.sort(
+ (a, b) => b.data.publicationDate.valueOf() - a.data.publicationDate.valueOf(),
+);
+
+const title = "全部节目|AsyncTalk";
+const description =
+ "浏览 AsyncTalk 全部中文播客节目,涵盖 Web 开发、前端工程化、JavaScript、AI、开发者工具与技术职业成长。";
+const dateFormatter = new Intl.DateTimeFormat("zh-CN", {
+ year: "numeric",
+ month: "long",
+ day: "numeric",
+ timeZone: "Asia/Shanghai",
});
---
-
+
-
+
-
-
diff --git a/src/pages/posts/[...slug]-og.png.ts b/src/pages/posts/[...slug]-og.png.ts
index 6c7a37f..fbe5dea 100644
--- a/src/pages/posts/[...slug]-og.png.ts
+++ b/src/pages/posts/[...slug]-og.png.ts
@@ -5,7 +5,10 @@ import { getCollection } from "astro:content";
// import fs from "fs/promises";
export async function getStaticPaths() {
- const blog = await getCollection("posts");
+ const blog = await getCollection(
+ "posts",
+ ({ data }) => !import.meta.env.PROD || data.status === "published",
+ );
// const blogData = await getBlogFrontmatterCollection();
return blog.map((post) => {
const title = post.data.title
@@ -40,6 +43,7 @@ export const GET: APIRoute = async function get({ props }) {
return new Response(png as unknown as ReadableStream, {
headers: {
"Content-Type": "image/png",
+ "Cache-Control": "public, max-age=86400, s-maxage=604800",
},
});
};
diff --git a/src/pages/posts/[...slug].astro b/src/pages/posts/[...slug].astro
index c074eae..a839194 100644
--- a/src/pages/posts/[...slug].astro
+++ b/src/pages/posts/[...slug].astro
@@ -22,9 +22,22 @@ import AdSense from "../../components/AdSense.astro";
import VideoEmbed from "../../components/VideoEmbed.astro";
import SlidesEmbed from "../../components/SlidesEmbed.astro";
import { SLIDES_BASE_URL } from "../../constants";
+import {
+ SITE_URL,
+ bilibiliUrl as getBilibiliUrl,
+ canonicalUrl,
+ compactUrls,
+ episodePath,
+ jsonLd,
+ parseAuthors,
+ youtubeUrl,
+} from "../../lib/seo";
export async function getStaticPaths() {
- const posts = await getCollection("posts");
+ const posts = await getCollection(
+ "posts",
+ ({ data }) => !import.meta.env.PROD || data.status === "published",
+ );
return posts.map((post) => ({
params: {
@@ -48,35 +61,66 @@ const tags = frontData.categories;
const slug = post.id.replace(/^\/?posts\//, "");
const slidesSrc =
frontData.slidesUrl ?? `${SLIDES_BASE_URL}/${slug}/`;
-const imgUrl = `${Astro.site?.origin}${post.id}-og.png`;
+const pagePath = episodePath(post.id);
+const pageUrl = canonicalUrl(pagePath, Astro.site);
+const imgUrl = new URL(
+ `${pagePath.replace(/\/$/, "")}-og.png`,
+ Astro.site ?? SITE_URL,
+).toString();
+const podcastId = `${new URL("/", Astro.site ?? SITE_URL)}#podcast`;
+const organizationId = `${new URL("/", Astro.site ?? SITE_URL)}#organization`;
+const authorEntities = parseAuthors(frontData.author);
+const sameAs = compactUrls([
+ frontData.xyzLink || undefined,
+ youtubeUrl(frontData.youtubeId),
+ getBilibiliUrl(frontData.biliUrl),
+]);
+const title = `${frontData.title}|AsyncTalk`;
---
-
+