news 2026/9/15 10:00:53

TinaCMS Astro 集成如何注册 TinaIsland 添加新的可编辑区域

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
TinaCMS Astro 集成如何注册 TinaIsland 添加新的可编辑区域

TinaCMS Astro 集成如何注册 TinaIsland 添加新的可编辑区域

【免费下载链接】tinacmsTinaCMS is the leading open-source headless CMS that supports Markdown and Visual Editing. Your content is stored in your own GitHub repo 🦙 ❤️项目地址: https://gitcode.com/GitHub_Trending/ti/tinacms

当你已经用@tinacms/astro把 TinaCMS 可视编辑接入 Astro 站点,并且至少有一个可编辑区域(island)在正常运行时,接下来常见的需求就是:再增加一个新的可编辑区域——比如新增一种内容类型的详情页,或者把页面上的某个模块单独抽出来让编辑者能点选刷新。这篇基于仓库中@tinacms/astro包的 GETTING_STARTED.md 和参考实现examples/astro/visual-editing的正文,讲清"新增一个 island"要改哪几处文件、每处怎么改、改完怎么验证。

适用环境:Astro 5/6/7、Node 18+、TinaCMS 3,项目已安装@tinacms/astrotinacms@tinacms/cli和一个 SSR adapter(如@astrojs/node),且astro.config.mjs里已经挂了tina()integration。

开始前确认已有基础

新增 island 的前提是项目已经走通了集成主路径,对应 GETTING_STARTED.md 的第 1–4 步:

  • astro.config.mjs中通过integrations: [tina()]引入集成,它会自动注入 middleware 并把 vanilla-JS bridge 作为静态文件放在/admin/bridge.js
  • 存在src/pages/tina-island/[name].ts这个动态路由,且export const prerender = false
  • 数据加载函数统一用requestWithMetadata()包裹生成的 Tina client 查询,否则编辑态下 overlay 数据不会进入渲染。

参考实现中的 data.ts 展示了标准写法:每个 collection 一个 loader,查询走tina/__generated__/client

import { requestWithMetadata } from '@tinacms/astro'; import client from '../../tina/__generated__/client'; export const getPost = (slug: string) => requestWithMetadata(client.queries.post({ relativePath: `${slug}.md` }));

如果你要新增的是全新 collection,先在tina/collections/配好 collection,再用pnpm tinacms init或重建生成客户端;如果只是复用已有 collection,直接进入下一步。

第一步:编写区域组件

为新的可编辑区域写一个独立的.astro组件,接收该区域需要的数据作为 props。参考实现中 islands/ 目录下的PostBody.astro等组件就是这种形态。以 PostBody.astro 为例,它做两件事:

  • 富文本正文用TinaMarkdown渲染(注意要从@tinacms/astro/TinaMarkdown.astro这个子路径导入,裸包的默认导出解析到的是一个占位类型,astro check会报Type 'X' is not assignable to type 'never');
  • 在想要"点击即聚焦"的元素上加data-tina-field={tinaField(data, '字段名')}标记:
--- import TinaMarkdown from '@tinacms/astro/TinaMarkdown.astro'; import { tinaField } from '@tinacms/astro/tina-field'; import type { PostQuery } from '../../../tina/__generated__/types'; interface Props { data: NonNullable<PostQuery['post']>; } const { data } = Astro.props; --- {data.title && ( <h1>import type { IslandRegistry } from '@tinacms/astro/experimental'; import AuthorBody from '../components/islands/AuthorBody.astro'; import { getAuthor } from './data'; export const islands: IslandRegistry = { // 已有条目:page、post、blog、global、global-footer … author: { fetch: (_request, params) => getAuthor(params.get('filename') ?? ''), component: AuthorBody, wrapper: SECTION_WRAPPER, propsFromData: (data) => ({ data: (data as { data?: { author?: unknown } }).data?.author, }), }, };

四个字段各自的作用(依据 island-route.ts 中的注释):

  • fetch: (request, params) => Promise<unknown>— 解析组件所需数据,params是 URL search params,可以忽略;返回的就是 loader 的结果;
  • component— 用取回的数据渲染的 Astro 组件;
  • wrapper: { tag; className? }— bridge 替换进 DOM 时套用的外层元素,必须与页面侧<TinaIsland>传入的 wrapper 一致
  • propsFromData: (data, params) => props— 把取回的数据和 URL 参数映射为组件 props。

新增一个可编辑区域时,只需要往这个 map 里加一个条目;动态路由会自动按 URL 里的 name 去查表,不需要为每个 island 写新路由。

第三步:在页面里用<TinaIsland>包裹区域

在渲染该区域的页面中引入组件并用<TinaIsland>包住,参考实现 posts/[...urlSegments].astro 的写法:

--- import TinaIsland from '@tinacms/astro/TinaIsland.astro'; import PostBody from '../../components/islands/PostBody.astro'; import { getPost } from '../../lib/data'; import { islands } from '../../lib/islands'; const slug = (Astro.params.urlSegments ?? '').toString(); const post = await getPost(slug); if (!post.data?.post) { return new Response('Not Found', { status: 404 }); } const data = post.data.post; --- <TinaIsland name="post" wrapper={islands.post.wrapper} params={{ slug }} primary> <PostBody data={data} /> </TinaIsland>

<TinaIsland>的 props(见 TinaIsland.astro):

  • name— 与 registry 中的 key 一致,决定 bridge 去请求哪个/tina-island/[name]端点;
  • wrapper— 直接复用islands.xxx.wrapper,保证与 registry 条目一致;
  • params— 会序列化成 search params 传给fetchpropsFromData(例如{ slug });
  • primary— 标记该区域是页面主表单。output: 'server'下可省略:第一次requestWithMetadata()调用(通常就是页面自己的)会被自动视为 primary;output: 'static'下必须显式指定,否则编辑器的落点可能停在 "Referenced Files" 列表上。每页最多标一个primary

路由不用动,但要保留prerender = false

动态端点只有一个通用文件 tina-island/[name].ts,它通过experimental_createIslandRoute(islands)从注册表取对应条目渲染:

import type { APIRoute } from 'astro'; import { experimental_createIslandRoute } from '@tinacms/astro/experimental'; import { islands } from '../../lib/islands'; export const prerender = false; export const ALL: APIRoute = experimental_createIslandRoute(islands);

新增 island 后这个文件不需要任何改动。唯一要注意的是保持export const prerender = false——bridge 对该端点的请求必须由 adapter 按需渲染,静态构建下它必须仍然是运行时路由。

运行与验证

启动命令(GETTING_STARTED.md 第 9、10 步):

pnpm dev # Site: http://localhost:4321/ # Admin: http://localhost:4321/admin/

验证方式:打开/admin/,点进一篇文档编辑某个字段。按文档描述,成功时 iframe 只会重新拉取受影响的 island 并把它换进 DOM,整页不刷新、页面树里没有 React。针对你新增的区域,可以在 DevTools 里进一步确认:

  • 打开该页面后查看<head>,编辑模式下应包含<div contenteditable="false">【免费下载链接】tinacmsTinaCMS is the leading open-source headless CMS that supports Markdown and Visual Editing. Your content is stored in your own GitHub repo 🦙 ❤️项目地址: https://gitcode.com/GitHub_Trending/ti/tinacms

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 9:58:19

FiftyOne 视觉AI数据集构建与评估指南

FiftyOne 视觉AI数据集构建与评估指南 【免费下载链接】fiftyone Refine high-quality datasets and visual AI models 项目地址: https://gitcode.com/GitHub_Trending/fi/fiftyone FiftyOne 是一款面向计算机视觉团队的数据集构建与模型评估工具&#xff0c;核心解决视…

作者头像 李华
网站建设 2026/9/15 9:57:48

PHPMailer 6.9 如何用 XCLIENT 扩展属性向 Postfix 网关传递转发身份

PHPMailer 6.9 如何用 XCLIENT 扩展属性向 Postfix 网关传递转发身份 【免费下载链接】PHPMailer The classic email sending library for PHP 项目地址: https://gitcode.com/GitHub_Trending/ph/PHPMailer 当你的 PHP 应用不直接连外网邮件服务器&#xff0c;而是先把…

作者头像 李华
网站建设 2026/9/15 9:57:14

豆包AI生图生视频高效实战:提示词优化与ComfyUI工作流

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/15 9:56:49

深入Celery worker ping:control命令族底层原理与生产排障实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/15 9:55:08

纯真CZDB与GeoLite2深度对比:IP归属地库选型实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华