diff --git a/.changeset/fuzzy-rivers-search.md b/.changeset/fuzzy-rivers-search.md
index 06e2dda6d7..4a82d1884c 100644
--- a/.changeset/fuzzy-rivers-search.md
+++ b/.changeset/fuzzy-rivers-search.md
@@ -2,4 +2,49 @@
'@backstage/core-app-api': minor
---
-Added a new `AppRouter` component that replaces the same component currently created through `app.getRouter()`.
+Added a new `AppRouter` component and `app.createRoot()` method that replaces `app.getRouter()` and `app.getProvider()`, which are now deprecated. The new `AppRouter` component is a drop-in replacement for the old router component, while the new `app.createRoot()` method is used instead of the old provider component.
+
+An old app setup might look like this:
+
+```tsx
+const app = createApp(/* ... */);
+
+const AppProvider = app.getProvider();
+const AppRouter = app.getRouter();
+
+const routes = ...;
+
+const App = () => (
+
+
+
+
+ {routes}
+
+
+);
+
+export default App;
+```
+
+With these new APIs, the setup now looks like this:
+
+```tsx
+import { AppRouter } from '@backstage/core-app-api';
+
+const app = createApp(/* ... */);
+
+const routes = ...;
+
+export default app.createRoot(
+ <>
+
+
+
+ {routes}
+
+ >,
+);
+```
+
+Note that `app.createRoot()` accepts a React element, rather than a component.
diff --git a/packages/core-app-api/api-report.md b/packages/core-app-api/api-report.md
index 137d6d72ca..c07ea55b67 100644
--- a/packages/core-app-api/api-report.md
+++ b/packages/core-app-api/api-report.md
@@ -262,6 +262,7 @@ export type AuthApiCreateOptions = {
export type BackstageApp = {
getPlugins(): BackstagePlugin[];
getSystemIcon(key: string): IconComponent | undefined;
+ createRoot(element: JSX.Element): ComponentType<{}>;
getProvider(): ComponentType<{}>;
getRouter(): ComponentType<{}>;
};
diff --git a/packages/core-app-api/src/app/AppManager.tsx b/packages/core-app-api/src/app/AppManager.tsx
index a5c981a54e..5a752ad5df 100644
--- a/packages/core-app-api/src/app/AppManager.tsx
+++ b/packages/core-app-api/src/app/AppManager.tsx
@@ -248,7 +248,23 @@ export class AppManager implements BackstageApp {
return this.components;
}
+ createRoot(element: JSX.Element): ComponentType<{}> {
+ const AppProvider = this.getProvider();
+ const AppRoot = () => {
+ return {element};
+ };
+ return AppRoot;
+ }
+
+ #getProviderCalled = false;
getProvider(): ComponentType<{}> {
+ if (this.#getProviderCalled) {
+ throw new Error(
+ 'app.getProvider() or app.createRoot() has already been called, and can only be called once',
+ );
+ }
+ this.#getProviderCalled = true;
+
const appContext = new AppContextImpl(this);
// We only validate routes once
diff --git a/packages/core-app-api/src/app/types.ts b/packages/core-app-api/src/app/types.ts
index 861766f8d3..82c05ce268 100644
--- a/packages/core-app-api/src/app/types.ts
+++ b/packages/core-app-api/src/app/types.ts
@@ -298,9 +298,36 @@ export type BackstageApp = {
*/
getSystemIcon(key: string): IconComponent | undefined;
+ /**
+ * Creates the root component that renders the entire app.
+ *
+ * @remarks
+ *
+ * This method must only be called once, and you have to provide it the entire
+ * app element tree. The element tree will be analyzed to discover plugins,
+ * routes, and other app features. The returned component will render all
+ * of the app elements wrapped within the app context provider.
+ *
+ * @example
+ * ```tsx
+ * export default app.createRoot(
+ * <>
+ *
+ *
+ *
+ * {routes}
+ *
+ * >,
+ * );
+ * ```
+ */
+ createRoot(element: JSX.Element): ComponentType<{}>;
+
/**
* Provider component that should wrap the Router created with getRouter()
* and any other components that need to be within the app context.
+ *
+ * @deprecated Use {@link BackstageApp.createRoot} instead.
*/
getProvider(): ComponentType<{}>;