Token导航 LogoToken导航TokenDH.com
前端设计执行命令unknown未标认证来源可访问许可证需确认审计未展示

plugin-new-frontend-system-support插件新前端系统支持

Agent Skill

用于辅助前端页面、组件、样式和交互逻辑的开发与维护。它适合让 Agent 生成或审查 React、Next.js、Vue、Tailwind、CSS 等相关代码,整理组件结构,或定位布局和性能问题。使用时需要结合项目现有设计系统、路由和构建方式,避免只生成孤立片段;涉及页面改动时,应配合本地预览和构建检查确认视觉效果。

总安装

1,722

周安装

69

下载量

558
Local Agent

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

来源数

2

许可证

unknown

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:plugin-new-frontend-system-support(插件新前端系统支持)
来源仓库:https://backstage.io
仓库路径:plugin-new-frontend-system-support
安装命令:
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。当前暂无明确安装命令,请以来源页面说明为准。

简介

用于辅助前端页面、组件、样式和交互逻辑的开发与维护。

  • 适合生成或审查 React、Next.js、Vue、Tailwind、CSS 等代码,整理组件结构或定位布局问题。
  • 使用时需结合项目设计系统、路由和构建方式,避免生成孤立片段;涉及页面改动应配合预览和构建检查。
  • 安装方式未知,建议确认宿主环境兼容性后再使用。
  • 注意:涉及真实页面修改时,务必通过本地预览验证视觉效果,防止样式错乱或功能异常。

SKILL.md

Adding New Frontend System Support to an Existing Plugin

This skill helps add new frontend system (NFS) support to an existing Backstage plugin while keeping the old system fully functional. The result is a plugin that works in both old and new apps via a dual entry point pattern.

This is the preferred approach for published plugins or plugins that are used by external parties, since it avoids forcing consumers to migrate their app before they are ready.

It is highly recommended to be on Backstage version 1.49.x or above before starting this, although not mandatory, you may face issues with some of the instructions below. This can be verified by looking in the backstage.json file in the root of the repository.

Key Concepts

  • Dual entry point: The plugin keeps its existing src/plugin.ts (old system) and adds a new src/alpha.tsx (new system)
  • Old system: createPlugin from @backstage/core-plugin-api, pages via createRoutableExtension, routes defined in the app
  • New system: createFrontendPlugin from @backstage/frontend-plugin-api, pages via PageBlueprint, routes owned by the plugin
  • Dual header pattern: Old system uses Page/Header/PageWithHeader from @backstage/core-components; new system relies on the framework's PageLayout which renders PluginHeader from @backstage/ui — so NFS page components should NOT include their own page shell

Step 1: Create the Alpha Entry Point

Create src/alpha.tsx (or src/alpha/index.ts for larger plugins) with a createFrontendPlugin default export:

// src/alpha.tsx
import {
  createFrontendPlugin,
  PageBlueprint,
} from '@backstage/frontend-plugin-api';
import { RiToolsLine } from '@remixicon/react';
import { rootRouteRef } from './routes';

const myPage = PageBlueprint.make({
  params: {
    path: '/my-plugin',
    routeRef: rootRouteRef,
    loader: () => import('./components/MyPage').then(m => <m.NfsMyPage />),
  },
});

export default createFrontendPlugin({
  pluginId: 'my-plugin',
  title: 'My Plugin',
  icon: <RiToolsLine />,
  extensions: [myPage],
  routes: {
    root: rootRouteRef,
  },
  externalRoutes: {
    // same external routes as the old plugin
  },
});

For the plugin icon, prefer using Remix Icons from @remixicon/react. If the plugin already has an existing MUI icon, it can be kept with fontSize="inherit" (e.g. <CategoryIcon fontSize="inherit" />), but for new icons Remix is the recommended choice.

The title and icon params on PageBlueprint are only needed if they should differ from the plugin's own title and icon (set in createFrontendPlugin). If omitted, the plugin-level values are used.

For larger plugins, organize into src/alpha/plugin.tsx, src/alpha/pages.tsx, src/alpha/extensions.tsx, etc., and re-export from src/alpha/index.ts.

Step 2: Update package.json Exports

Add the ./alpha subpath export and its typesVersions entry:

{
  "exports": {
    ".": "./src/index.ts",
    "./alpha": "./src/alpha.tsx",
    "./package.json": "./package.json"
  },
  "typesVersions": {
    "*": {
      "alpha": ["src/alpha.tsx"],
      "package.json": ["package.json"]
    }
  }
}

Apps import the new plugin as:

import myPlugin from '@backstage/plugin-my-plugin/alpha';

Step 3: Implement the Dual Header Pattern

The critical difference between old and new system page components is the page shell. In the old system, each page renders its own Page + Header (or PageWithHeader) wrapper. In the new system, the framework's PageLayout provides the header via PluginHeader automatically — so the NFS page component must not include its own page shell.

Pattern A: Separate Components (Recommended for Simple Pages)

Create two exported components — one for each system:

// src/components/MyPage/MyPage.tsx
import {
  Content,
  PageWithHeader,
  ContentHeader,
  SupportButton,
} from '@backstage/core-components';
import { Header } from '@backstage/ui';

// Used by the OLD system — includes the full page shell
export function MyPage() {
  return (
    <PageWithHeader title="My Plugin" themeId="tool">
      <Content>
        <ContentHeader title="">
          <SupportButton>Some help text</SupportButton>
        </ContentHeader>
        <MyPageContent />
      </Content>
    </PageWithHeader>
  );
}

// Used by the NEW system — no page shell, just content
// The framework's PageLayout/PluginHeader provides the title and header
export function NfsMyPage() {
  return (
    <>
      <Header
        title="My Plugin Subtitle"
        customActions={<SupportButton>Some help text</SupportButton>}
      />
      <Content>
        <MyPageContent />
      </Content>
    </>
  );
}

Key differences in the NFS variant:

  • No Page/PageWithHeader — the framework provides the outer page shell
  • Header from @backstage/ui is optional — use it only if you need a subtitle or custom actions below the framework header
  • No ContentHeader — actions move to Header's customActions prop
  • The shared <MyPageContent /> component contains the actual page body

Forwarding Customization Props

If the old system exports a page component with props for customization (e.g. <CatalogIndexPage actions={...} filters={...} />), the NFS variant should accept the same props. Export the NFS variant with the same component name from the ./alpha entry point, so that app adopters can customize it the same way:

// src/components/MyPage/MyPage.tsx
export interface MyPageProps {
  actions?: ReactNode;
  filters?: ReactNode;
}

// Old system — exported from src/index.ts
export function MyPage(props: MyPageProps) {
  return (
    <PageWithHeader title="My Plugin" themeId="tool">
      <Content>
        <MyPageContent {...props} />
      </Content>
    </PageWithHeader>
  );
}

// NFS variant — exported from src/alpha.tsx
export function NfsMyPage(props: MyPageProps) {
  return (
    <Content>
      <MyPageContent {...props} />
    </Content>
  );
}

The NFS variant is then wired into the PageBlueprint loader, and the component itself is re-exported from ./alpha so adopters can use .withOverrides() to pass custom props:

// src/alpha.tsx
export { NfsMyPage as MyPage } from './components/MyPage';

This way, the old MyPage is available from the main entry point, and the same name MyPage is available from ./alpha — both accepting the same props for customization.

Pattern B: Header Variant Prop (Recommended for Complex Pages)

For pages with significant shared logic, use a headerVariant prop pattern:

// src/components/MyPage/MyPage.tsx
import { Content, PageWithHeader } from '@backstage/core-components';

function MyPageContent(
  props: MyPageProps & { headerVariant: 'legacy' | 'bui' },
) {
  const { headerVariant, ...rest } = props;

  // ... shared page logic, data fetching, etc.

  const pageContent = <Content>{/* shared page body */}</Content>;

  if (headerVariant === 'bui') {
    return pageContent;
  }

  return (
    <PageWithHeader title="My Plugin" themeId="tool">
      {pageContent}
    </PageWithHeader>
  );
}

// Old system export
export const MyPage = (props: MyPageProps) => (
  <MyPageContent {...props} headerVariant="legacy" />
);

// New system export
export const NfsMyPage = (props: MyPageProps) => (
  <MyPageContent {...props} headerVariant="bui" />
);

Pattern C: Content-Only Sub-Pages (For Tabbed Plugins)

When using SubPageBlueprint for tabbed pages, sub-page loaders should render only the content — the parent PageBlueprint provides the header and tabs:

// src/alpha/extensions.tsx
import {
  PageBlueprint,
  SubPageBlueprint,
} from '@backstage/frontend-plugin-api';

export const myPluginPage = PageBlueprint.make({
  params: {
    path: '/my-plugin',
    routeRef: rootRouteRef,
  },
});

export const overviewSubPage = SubPageBlueprint.make({
  name: 'overview',
  params: {
    path: 'overview',
    title: 'Overview',
    loader: () =>
      import('../components/OverviewPage').then(m => <m.OverviewPageContent />),
  },
});

export const settingsSubPage = SubPageBlueprint.make({
  name: 'settings',
  params: {
    path: 'settings',
    title: 'Settings',
    loader: () =>
      import('../components/SettingsPage').then(m => <m.SettingsPageContent />),
  },
});

Note: when using SubPageBlueprint, omit the loader from PageBlueprint to use the built-in tabbed sub-page rendering. The PageBlueprint without a loader creates a parent page that renders sub-pages as tabs automatically. If the sub-page content needs padding, use Container from @backstage/ui as a wrapper inside the component.

Step 4: Migrate APIs to ApiBlueprint

APIs that were part of the old createPlugin({apis: [...]}) become ApiBlueprint extensions added to the plugin's extensions array.

API Ownership

In the new system, each API has an owner plugin that controls who can provide or override it. Ownership can be set explicitly via pluginId on the ApiRef (recommended), or inferred from the ApiRef ID string pattern:

  • Explicit pluginId on the ref → that plugin owns it
  • plugin.<pluginId>.* ID → owned by that plugin
  • core.* ID → owned by the app plugin

The recommended way to define API refs in the new system uses the builder pattern with an explicit pluginId:

// In your -react package
import { createApiRef } from '@backstage/frontend-plugin-api';

export const myPluginApiRef = createApiRef<MyPluginApi>().with({
  id: 'plugin.my-plugin.client',
  pluginId: 'my-plugin',
});

When your plugin provides an ApiBlueprint in its extensions array, the extension is automatically namespaced under your plugin — so the ownership is correct by default:

// src/alpha/apis.ts
import {
  ApiBlueprint,
  discoveryApiRef,
  fetchApiRef,
} from '@backstage/frontend-plugin-api';
import { myPluginApiRef } from '@internal/plugin-my-plugin-react';
import { MyPluginClient } from '../api';

export const myPluginApi = ApiBlueprint.make({
  params: defineParams =>
    defineParams({
      api: myPluginApiRef,
      deps: {
        discoveryApi: discoveryApiRef,
        fetchApi: fetchApiRef,
      },
      factory: ({ discoveryApi, fetchApi }) =>
        new MyPluginClient({ discoveryApi, fetchApi }),
    }),
});

Add the API extension to the plugin's extensions array.

App adopters who want to override your plugin's API must do so using a createFrontendModule targeting your plugin's ID — they cannot override it from a module for a different plugin.

Step 5: Route Refs

Reusing Existing Route Refs

Route refs defined using createRouteRef from @backstage/core-plugin-api can be used directly in the new system — no conversion needed. They work when passed to createFrontendPlugin's routes/externalRoutes and to PageBlueprint's routeRef param:

// routes.ts — keep using your existing route refs from @backstage/core-plugin-api
import { createRouteRef } from '@backstage/core-plugin-api';
export const rootRouteRef = createRouteRef({ id: 'my-plugin' });

// alpha.tsx — pass them directly, no conversion needed
const myPage = PageBlueprint.make({
  params: {
    path: '/my-plugin',
    routeRef: rootRouteRef,
    loader: () => import('./MyPage').then(m => <m.NfsMyPage />),
  },
});

There is no need for convertLegacyRouteRef or compatWrapper from @backstage/core-compat-api — these are no longer required for plugin migration.

Default Targets for External Route Refs

When adding new-system support, set defaultTarget on your external route refs so that apps don't need explicit route bindings for common cases. The target string uses the <pluginId>.<routeName> format, matching the routes map of the target plugin. The default is only used when the target plugin is actually installed — otherwise the route remains unbound.

// routes.ts
import { createExternalRouteRef } from '@backstage/core-plugin-api';

export const viewTechDocRouteRef = createExternalRouteRef({
  id: 'view-techdoc',
  optional: true,
  params: ['namespace', 'kind', 'name'],
  defaultTarget: 'techdocs.docRoot',
});

export const createComponentRouteRef = createExternalRouteRef({
  id: 'create-component',
  optional: true,
  defaultTarget: 'scaffolder.root',
});

This significantly improves the out-of-the-box experience — plugins with sensible defaults "just work" when installed without requiring the app to configure bindRoutes.

useRouteRef Behavior Difference

In the new system, useRouteRef from @backstage/frontend-plugin-api may return undefined for unbound external routes. Legacy useRouteRef from @backstage/core-plugin-api throws an error instead. When writing NFS components, handle the undefined case.

Step 6: Translations

If the plugin uses translations, the translation ref should be exported from the main entry point (src/index.ts). There is no need to re-export it from ./alpha — consumers import translation refs from the main entry point regardless of which frontend system they use.

The same applies to other refs like API refs and route refs: keep them exported from the main entry point (or the -react package) and avoid duplicating exports in ./alpha.

Real Examples from the Backstage Repo

Catalog Plugin (Dual Entry Point)

  • Old: plugins/catalog/src/plugin.tscreatePlugin with createRoutableExtension
  • New: plugins/catalog/src/alpha/plugin.tsxcreateFrontendPlugin with PageBlueprint
  • Header split: plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx

- BaseCatalogPage (old) uses PageWithHeader + ContentHeader - NfsBaseCatalogPage (new) uses Header from @backstage/ui + Content

Scaffolder Plugin (Sub-Pages)

  • Old: plugins/scaffolder/src/plugin.tsx — single ScaffolderPage with internal routing
  • New: plugins/scaffolder/src/alpha/extensions.tsxPageBlueprint (no loader) + multiple SubPageBlueprint entries for templates, tasks, actions, editor
  • Sub-page loaders wrap content in <Content> only — no page shell

Notifications Plugin (Header Variant Pattern)

  • plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx
  • Uses headerVariant: 'legacy' | 'bui' prop
  • NfsNotificationsPage returns content only (no PageWithHeader)
  • NotificationsPage wraps in PageWithHeader

API Docs Plugin (Simple Dual Page)

  • plugins/api-docs/src/components/ApiExplorerPage/DefaultApiExplorerPage.tsx
  • DefaultApiExplorerPage (old) uses PageWithHeader + ContentHeader
  • NfsApiExplorerPage (new) uses Header + Content

Migration Checklist

  1. Create src/alpha.tsx (or src/alpha/ directory) with createFrontendPlugin
  2. Add ./alpha to package.json exports and typesVersions
  3. Create PageBlueprint for each top-level page
  4. Create SubPageBlueprint for tabbed sub-pages (if applicable)
  5. Convert API factories to ApiBlueprint extensions
  6. Implement NFS page variants without page shell (Page/Header/PageWithHeader)
  7. Use Header from @backstage/ui for subtitle/custom actions in NFS pages
  8. Wire route refs (existing @backstage/core-plugin-api refs work directly, no conversion needed)
  9. Ensure translation refs and API refs are exported from the main entry point (not duplicated in ./alpha)
  10. Add @backstage/frontend-plugin-api to package.json dependencies
  11. Add @backstage/ui to dependencies (if using Header)
  12. Run yarn tsc to check for type errors
  13. Run yarn lint to check for missing dependencies
  14. Test in both old app (packages/app-legacy) and new app (packages/app)
  15. Run yarn build:api-reports to update API reports (if the project uses API reports)

Reference

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Local Agent

73.15%
按下载量换算408

安全审计

暂无安全审计结果可展示。

权限和风险

执行命令

安装流程涉及命令执行,可能通过 第三方 CLI 联网下载 Skill 或依赖。用户安装前应确认命令来源、仓库内容和执行环境。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills