diff --git a/.changeset/fuzzy-suits-sit.md b/.changeset/fuzzy-suits-sit.md
new file mode 100644
index 0000000000..a5c8f6b7a0
--- /dev/null
+++ b/.changeset/fuzzy-suits-sit.md
@@ -0,0 +1,16 @@
+---
+'@backstage/plugin-techdocs': minor
+---
+
+Add 2 config elements to extension "page:techdocs/reader" to configure default layout `withSearch` and `withHeader`. Default are unchanged to `true`.
+
+E.g. to disable the search and header on the Techdocs Reader Page:
+
+```yaml
+app:
+ extensions:
+ - page:techdocs/reader:
+ config:
+ withSearch: false
+ withHeader: false
+```
diff --git a/.changeset/old-paths-jump.md b/.changeset/old-paths-jump.md
new file mode 100644
index 0000000000..7a5d9aef43
--- /dev/null
+++ b/.changeset/old-paths-jump.md
@@ -0,0 +1,5 @@
+---
+'@techdocs/cli': patch
+---
+
+Migrate the Techdocs CLI embedded app to the New Frontend System (NFS)
diff --git a/packages/techdocs-cli-embedded-app/app-config.yaml b/packages/techdocs-cli-embedded-app/app-config.yaml
index c8dfa1683c..be60d56756 100644
--- a/packages/techdocs-cli-embedded-app/app-config.yaml
+++ b/packages/techdocs-cli-embedded-app/app-config.yaml
@@ -1,6 +1,11 @@
app:
title: Techdocs Preview App
baseUrl: http://localhost:3000
+ extensions:
+ - sign-in-page:app: false
+ - page:techdocs/reader:
+ config:
+ withSearch: false
backend:
baseUrl: http://localhost:3000
diff --git a/packages/techdocs-cli-embedded-app/knip-report.md b/packages/techdocs-cli-embedded-app/knip-report.md
index 3711ac996d..1b6bfe5aaa 100644
--- a/packages/techdocs-cli-embedded-app/knip-report.md
+++ b/packages/techdocs-cli-embedded-app/knip-report.md
@@ -1,17 +1,9 @@
# Knip report
-## Unused dependencies (2)
+## Unused devDependencies (2)
-| Name | Location | Severity |
-| :-------- | :----------- | :------- |
-| react-use | packages/techdocs-cli-embedded-app/package.json | error |
-| history | packages/techdocs-cli-embedded-app/package.json | error |
-
-## Unused devDependencies (3)
-
-| Name | Location | Severity |
-| :-------------------------- | :----------- | :------- |
-| @testing-library/user-event | packages/techdocs-cli-embedded-app/package.json | error |
-| @testing-library/dom | packages/techdocs-cli-embedded-app/package.json | error |
-| cross-env | packages/techdocs-cli-embedded-app/package.json | error |
+| Name | Location | Severity |
+| :-------------------------- | :---------------- | :------- |
+| @testing-library/user-event | package.json:61:6 | error |
+| cross-env | package.json:64:6 | error |
diff --git a/packages/techdocs-cli-embedded-app/package.json b/packages/techdocs-cli-embedded-app/package.json
index 99aa3bb222..62503eed6d 100644
--- a/packages/techdocs-cli-embedded-app/package.json
+++ b/packages/techdocs-cli-embedded-app/package.json
@@ -32,13 +32,13 @@
},
"prettier": "@backstage/cli/config/prettier",
"dependencies": {
- "@backstage/app-defaults": "workspace:^",
"@backstage/catalog-model": "workspace:^",
"@backstage/cli": "workspace:^",
"@backstage/config": "workspace:^",
"@backstage/core-app-api": "workspace:^",
"@backstage/core-components": "workspace:^",
- "@backstage/core-plugin-api": "workspace:^",
+ "@backstage/frontend-defaults": "workspace:^",
+ "@backstage/frontend-plugin-api": "workspace:^",
"@backstage/integration-react": "workspace:^",
"@backstage/plugin-catalog": "workspace:^",
"@backstage/plugin-techdocs": "workspace:^",
@@ -48,11 +48,9 @@
"@backstage/ui": "workspace:^",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
- "history": "^5.0.0",
"react": "^18.0.2",
"react-dom": "^18.0.2",
- "react-router-dom": "^6.3.0",
- "react-use": "^17.2.4"
+ "react-router-dom": "^6.3.0"
},
"devDependencies": {
"@backstage/cli": "workspace:^",
diff --git a/packages/techdocs-cli-embedded-app/src/App.test.tsx b/packages/techdocs-cli-embedded-app/src/App.test.tsx
index ed479e7e75..4a7eaef911 100644
--- a/packages/techdocs-cli-embedded-app/src/App.test.tsx
+++ b/packages/techdocs-cli-embedded-app/src/App.test.tsx
@@ -15,13 +15,27 @@
*/
import { renderWithEffects } from '@backstage/test-utils';
-import App from './App';
+import app from './App';
jest.mock('./config', () => ({
configLoader: async () => [
{
data: {
- app: { title: 'Test' },
+ app: {
+ title: 'Test',
+ extensions: [
+ {
+ 'sign-in-page:app': false,
+ },
+ {
+ 'page:techdocs/reader': {
+ config: {
+ withSearch: false,
+ },
+ },
+ },
+ ],
+ },
backend: { baseUrl: 'http://localhost:7007' },
techdocs: {
storageUrl: 'http://localhost:7007/api/techdocs/static/docs',
@@ -34,7 +48,7 @@ jest.mock('./config', () => ({
describe('App', () => {
it('should render', async () => {
- const rendered = await renderWithEffects();
+ const rendered = await renderWithEffects(app);
expect(rendered.getByText('Docs Preview')).toBeInTheDocument();
});
});
diff --git a/packages/techdocs-cli-embedded-app/src/App.tsx b/packages/techdocs-cli-embedded-app/src/App.tsx
index 13d5b925f0..0c8fdea781 100644
--- a/packages/techdocs-cli-embedded-app/src/App.tsx
+++ b/packages/techdocs-cli-embedded-app/src/App.tsx
@@ -14,85 +14,41 @@
* limitations under the License.
*/
-import { Navigate, Route } from 'react-router-dom';
+import techdocsPlugin from '@backstage/plugin-techdocs/alpha';
-import {
- DefaultTechDocsHome,
- TechDocsIndexPage,
- TechDocsReaderPage,
- techdocsPlugin,
-} from '@backstage/plugin-techdocs';
-import {
- createTechDocsAddonExtension,
- TechDocsAddons,
- TechDocsAddonLocations,
-} from '@backstage/plugin-techdocs-react';
-import { createApp } from '@backstage/app-defaults';
-import { FlatRoutes } from '@backstage/core-app-api';
-import { CatalogEntityPage } from '@backstage/plugin-catalog';
+import { createApp } from '@backstage/frontend-defaults';
+import { ConfigReader } from '@backstage/core-app-api';
+import catalogPlugin from '@backstage/plugin-catalog/alpha';
import { apis } from './apis';
-import * as plugins from './plugins';
import { configLoader } from './config';
-import { Root } from './components/Root';
-import { techDocsPage, TechDocsThemeToggle } from './components/TechDocsPage';
-import { TechDocsLiveReload } from './LiveReloadAddon';
-const app = createApp({
- apis,
- configLoader,
- plugins: Object.values(plugins),
+import { createFrontendModule } from '@backstage/frontend-plugin-api';
+import { SidebarContent } from './components/Root/Root';
+import {
+ techDocsThemeToggleAddonModule,
+ techdocsLiveReloadAddonModule,
+} from './addons';
+
+const appPlugin = createFrontendModule({
+ pluginId: 'app',
+ extensions: [...apis, SidebarContent],
});
-const AppProvider = app.getProvider();
-const AppRouter = app.getRouter();
+const app = createApp({
+ features: [
+ appPlugin,
+ techdocsPlugin,
+ catalogPlugin,
+ techDocsThemeToggleAddonModule,
+ techdocsLiveReloadAddonModule,
+ ],
+ advanced: {
+ async configLoader() {
+ const appConfigs = await configLoader();
+ return { config: ConfigReader.fromConfigs(appConfigs) };
+ },
+ },
+});
-const ThemeToggleAddon = techdocsPlugin.provide(
- createTechDocsAddonExtension({
- name: 'ThemeToggleAddon',
- component: TechDocsThemeToggle,
- location: TechDocsAddonLocations.Header,
- }),
-);
-
-const LiveReloadAddon = techdocsPlugin.provide(
- createTechDocsAddonExtension({
- name: 'LiveReloadAddon',
- component: TechDocsLiveReload,
- location: TechDocsAddonLocations.Content,
- }),
-);
-
-const routes = (
-
-
- {/* we need this route as TechDocs header links relies on it */}
- }
- />
- }>
-
-
- }
- >
- {techDocsPage}
-
-
-
-
-
-
-);
-
-const App = () => (
-
-
- {routes}
-
-
-);
-
-export default App;
+export default app.createRoot();
diff --git a/packages/techdocs-cli-embedded-app/src/addons.tsx b/packages/techdocs-cli-embedded-app/src/addons.tsx
new file mode 100644
index 0000000000..26ead00959
--- /dev/null
+++ b/packages/techdocs-cli-embedded-app/src/addons.tsx
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2026 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { AddonBlueprint } from '@backstage/plugin-techdocs-react/alpha';
+import { TechDocsAddonLocations } from '@backstage/plugin-techdocs-react';
+import { createFrontendModule } from '@backstage/frontend-plugin-api';
+import { TechDocsThemeToggle } from './components/TechDocsPage';
+import { TechDocsLiveReload } from './components/LiveReload/LiveReloadAddon';
+
+const techDocsThemeToggleAddonExtension = AddonBlueprint.make({
+ name: 'techdocs-theme-toggle-addon',
+ params: {
+ name: 'ThemeToggleAddon',
+ component: TechDocsThemeToggle,
+ location: TechDocsAddonLocations.Header,
+ },
+});
+
+export const techDocsThemeToggleAddonModule = createFrontendModule({
+ pluginId: 'techdocs',
+ extensions: [techDocsThemeToggleAddonExtension],
+});
+
+const techdocsLiveReloadAddonExtension = AddonBlueprint.make({
+ name: 'techdocs-live-reload-addon',
+ params: {
+ name: 'LiveReloadAddon',
+ component: TechDocsLiveReload,
+ location: TechDocsAddonLocations.Content,
+ },
+});
+
+export const techdocsLiveReloadAddonModule = createFrontendModule({
+ pluginId: 'techdocs',
+ extensions: [techdocsLiveReloadAddonExtension],
+});
diff --git a/packages/techdocs-cli-embedded-app/src/apis.ts b/packages/techdocs-cli-embedded-app/src/apis.ts
index 5bb19a563d..1b6387938b 100644
--- a/packages/techdocs-cli-embedded-app/src/apis.ts
+++ b/packages/techdocs-cli-embedded-app/src/apis.ts
@@ -13,6 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+import {
+ ApiBlueprint,
+ configApiRef,
+ DiscoveryApi,
+ discoveryApiRef,
+ IdentityApi,
+ identityApiRef,
+} from '@backstage/frontend-plugin-api';
import { CompoundEntityRef } from '@backstage/catalog-model';
import { Config } from '@backstage/config';
@@ -20,15 +28,6 @@ import {
scmIntegrationsApiRef,
ScmIntegrationsApi,
} from '@backstage/integration-react';
-import {
- AnyApiFactory,
- configApiRef,
- createApiFactory,
- DiscoveryApi,
- discoveryApiRef,
- IdentityApi,
- identityApiRef,
-} from '@backstage/core-plugin-api';
import {
SyncResult,
TechDocsApi,
@@ -158,38 +157,50 @@ class TechDocsDevApi implements TechDocsApi {
}
}
-export const apis: AnyApiFactory[] = [
- createApiFactory({
- api: techdocsStorageApiRef,
- deps: {
- configApi: configApiRef,
- discoveryApi: discoveryApiRef,
- identityApi: identityApiRef,
- },
- factory: ({ configApi, discoveryApi, identityApi }) =>
- new TechDocsDevStorageApi({
- configApi,
- discoveryApi,
- identityApi,
+export const apis = [
+ ApiBlueprint.make({
+ name: 'techdocs-dev-storage',
+ params: defineParams =>
+ defineParams({
+ api: techdocsStorageApiRef,
+ deps: {
+ configApi: configApiRef,
+ discoveryApi: discoveryApiRef,
+ identityApi: identityApiRef,
+ },
+ factory: ({ configApi, discoveryApi, identityApi }) =>
+ new TechDocsDevStorageApi({
+ configApi,
+ discoveryApi,
+ identityApi,
+ }),
}),
}),
- createApiFactory({
- api: techdocsApiRef,
- deps: {
- configApi: configApiRef,
- discoveryApi: discoveryApiRef,
- identityApi: identityApiRef,
- },
- factory: ({ configApi, discoveryApi, identityApi }) =>
- new TechDocsDevApi({
- configApi,
- discoveryApi,
- identityApi,
+ ApiBlueprint.make({
+ name: 'techdocs-dev',
+ params: defineParams =>
+ defineParams({
+ api: techdocsApiRef,
+ deps: {
+ configApi: configApiRef,
+ discoveryApi: discoveryApiRef,
+ identityApi: identityApiRef,
+ },
+ factory: ({ configApi, discoveryApi, identityApi }) =>
+ new TechDocsDevApi({
+ configApi,
+ discoveryApi,
+ identityApi,
+ }),
}),
}),
- createApiFactory({
- api: scmIntegrationsApiRef,
- deps: { configApi: configApiRef },
- factory: ({ configApi }) => ScmIntegrationsApi.fromConfig(configApi),
+ ApiBlueprint.make({
+ name: 'scm-integrations',
+ params: defineParams =>
+ defineParams({
+ api: scmIntegrationsApiRef,
+ deps: { configApi: configApiRef },
+ factory: ({ configApi }) => ScmIntegrationsApi.fromConfig(configApi),
+ }),
}),
];
diff --git a/packages/techdocs-cli-embedded-app/src/LiveReloadAddon.test.tsx b/packages/techdocs-cli-embedded-app/src/components/LiveReload/LiveReloadAddon.test.tsx
similarity index 100%
rename from packages/techdocs-cli-embedded-app/src/LiveReloadAddon.test.tsx
rename to packages/techdocs-cli-embedded-app/src/components/LiveReload/LiveReloadAddon.test.tsx
diff --git a/packages/techdocs-cli-embedded-app/src/LiveReloadAddon.tsx b/packages/techdocs-cli-embedded-app/src/components/LiveReload/LiveReloadAddon.tsx
similarity index 100%
rename from packages/techdocs-cli-embedded-app/src/LiveReloadAddon.tsx
rename to packages/techdocs-cli-embedded-app/src/components/LiveReload/LiveReloadAddon.tsx
diff --git a/packages/techdocs-cli-embedded-app/src/components/Root/Root.tsx b/packages/techdocs-cli-embedded-app/src/components/Root/Root.tsx
index 6a00fc78cf..c8dbe8634b 100644
--- a/packages/techdocs-cli-embedded-app/src/components/Root/Root.tsx
+++ b/packages/techdocs-cli-embedded-app/src/components/Root/Root.tsx
@@ -30,6 +30,7 @@ import {
useSidebarOpenState,
Link,
} from '@backstage/core-components';
+import { NavContentBlueprint } from '@backstage/frontend-plugin-api';
const useSidebarLogoStyles = makeStyles({
root: {
@@ -79,3 +80,8 @@ export const Root = ({ children }: PropsWithChildren<{}>) => (
{children}
);
+export const SidebarContent = NavContentBlueprint.make({
+ params: {
+ component: ({}) => ,
+ },
+});
diff --git a/packages/techdocs-cli-embedded-app/src/components/TechDocsPage/TechDocsPage.tsx b/packages/techdocs-cli-embedded-app/src/components/TechDocsPage/TechDocsPage.tsx
index dfe728da86..eae55d3424 100644
--- a/packages/techdocs-cli-embedded-app/src/components/TechDocsPage/TechDocsPage.tsx
+++ b/packages/techdocs-cli-embedded-app/src/components/TechDocsPage/TechDocsPage.tsx
@@ -25,7 +25,7 @@ import IconButton from '@material-ui/core/IconButton';
import LightIcon from '@material-ui/icons/Brightness7';
import DarkIcon from '@material-ui/icons/Brightness4';
-import { appThemeApiRef, useApi } from '@backstage/core-plugin-api';
+import { appThemeApiRef, useApi } from '@backstage/frontend-plugin-api';
import {
TechDocsReaderPage,
@@ -93,7 +93,7 @@ export const TechDocsThemeToggle = () => {
);
};
-const DefaultTechDocsPage = () => {
+export const DefaultTechDocsPage = () => {
return (
diff --git a/packages/techdocs-cli-embedded-app/src/index.tsx b/packages/techdocs-cli-embedded-app/src/index.tsx
index f50db079b0..84d77cc7e1 100644
--- a/packages/techdocs-cli-embedded-app/src/index.tsx
+++ b/packages/techdocs-cli-embedded-app/src/index.tsx
@@ -19,4 +19,4 @@ import ReactDOM from 'react-dom/client';
import '@backstage/ui/css/styles.css';
import App from './App';
-ReactDOM.createRoot(document.getElementById('root')!).render();
+ReactDOM.createRoot(document.getElementById('root')!).render(App);
diff --git a/packages/techdocs-cli-embedded-app/src/plugins.ts b/packages/techdocs-cli-embedded-app/src/plugins.ts
deleted file mode 100644
index 42fc16b339..0000000000
--- a/packages/techdocs-cli-embedded-app/src/plugins.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * Copyright 2020 The Backstage Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-export { plugin as TechDocsPlugin } from '@backstage/plugin-techdocs';
diff --git a/plugins/techdocs/report-alpha.api.md b/plugins/techdocs/report-alpha.api.md
index 96c778b09b..3e6a8945b3 100644
--- a/plugins/techdocs/report-alpha.api.md
+++ b/plugins/techdocs/report-alpha.api.md
@@ -265,9 +265,13 @@ const _default: OverridableFrontendPlugin<
}>;
'page:techdocs/reader': OverridableExtensionDefinition<{
config: {
+ withSearch: boolean;
+ withHeader: boolean;
path: string | undefined;
};
configInput: {
+ withSearch?: boolean | undefined;
+ withHeader?: boolean | undefined;
path?: string | undefined;
};
output:
diff --git a/plugins/techdocs/src/alpha/index.tsx b/plugins/techdocs/src/alpha/index.tsx
index d3fc94c7a8..b97bc7c97b 100644
--- a/plugins/techdocs/src/alpha/index.tsx
+++ b/plugins/techdocs/src/alpha/index.tsx
@@ -152,7 +152,13 @@ const techDocsReaderPage = PageBlueprint.makeWithOverrides({
inputs: {
addons: createExtensionInput([AddonBlueprint.dataRefs.addon]),
},
- factory(originalFactory, { inputs }) {
+ config: {
+ schema: {
+ withSearch: z => z.boolean().default(true),
+ withHeader: z => z.boolean().default(true),
+ },
+ },
+ factory(originalFactory, { inputs, config }) {
const addons = inputs.addons.map(output => {
const options = output.get(AddonBlueprint.dataRefs.addon);
const Addon = options.component;
@@ -166,7 +172,10 @@ const techDocsReaderPage = PageBlueprint.makeWithOverrides({
loader: async () =>
await import('../Router').then(({ TechDocsReaderRouter }) => (
-
+
{addons}
)),
diff --git a/yarn.lock b/yarn.lock
index b812d13734..67cf817550 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -47899,13 +47899,13 @@ __metadata:
version: 0.0.0-use.local
resolution: "techdocs-cli-embedded-app@workspace:packages/techdocs-cli-embedded-app"
dependencies:
- "@backstage/app-defaults": "workspace:^"
"@backstage/catalog-model": "workspace:^"
"@backstage/cli": "workspace:^"
"@backstage/config": "workspace:^"
"@backstage/core-app-api": "workspace:^"
"@backstage/core-components": "workspace:^"
- "@backstage/core-plugin-api": "workspace:^"
+ "@backstage/frontend-defaults": "workspace:^"
+ "@backstage/frontend-plugin-api": "workspace:^"
"@backstage/integration-react": "workspace:^"
"@backstage/plugin-catalog": "workspace:^"
"@backstage/plugin-techdocs": "workspace:^"
@@ -47922,11 +47922,9 @@ __metadata:
"@types/react": "npm:*"
"@types/react-dom": "npm:*"
cross-env: "npm:^10.0.0"
- history: "npm:^5.0.0"
react: "npm:^18.0.2"
react-dom: "npm:^18.0.2"
react-router-dom: "npm:^6.3.0"
- react-use: "npm:^17.2.4"
languageName: unknown
linkType: soft