diff --git a/examples/with-fallback-element/ice.config.mts b/examples/with-fallback-element/ice.config.mts
new file mode 100644
index 0000000000..63f3c0bcab
--- /dev/null
+++ b/examples/with-fallback-element/ice.config.mts
@@ -0,0 +1,5 @@
+import { defineConfig } from '@ice/app';
+
+export default defineConfig(() => ({
+ ssg: false,
+}));
diff --git a/examples/with-fallback-element/package.json b/examples/with-fallback-element/package.json
new file mode 100644
index 0000000000..d77d0fdb84
--- /dev/null
+++ b/examples/with-fallback-element/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "@ice/example-with-fallback-element",
+ "private": true,
+ "version": "1.0.0",
+ "description": "Example demonstrating custom fallbackElement configuration in app.tsx",
+ "dependencies": {
+ "react": "^18.0.0",
+ "react-dom": "^18.0.0",
+ "@ice/runtime": "workspace:*"
+ },
+ "devDependencies": {
+ "@ice/app": "workspace:*",
+ "@types/react": "^18.0.0",
+ "@types/react-dom": "^18.0.0"
+ },
+ "scripts": {
+ "start": "ice start",
+ "build": "ice build"
+ }
+}
diff --git a/examples/with-fallback-element/src/app.tsx b/examples/with-fallback-element/src/app.tsx
new file mode 100644
index 0000000000..b65ceaa975
--- /dev/null
+++ b/examples/with-fallback-element/src/app.tsx
@@ -0,0 +1,33 @@
+import React from 'react';
+import { defineAppConfig } from 'ice';
+
+// Custom fallback element.
+const CustomFallback = () => (
+
+
+
🔄 Loading...
+
+ Custom fallback element from app.tsx configuration
+
+
+
+);
+
+export default defineAppConfig({
+ app: {
+ rootId: 'app',
+ },
+ router: {
+ type: 'hash',
+ fallbackElement: ,
+ },
+});
diff --git a/examples/with-fallback-element/src/pages/home.tsx b/examples/with-fallback-element/src/pages/home.tsx
new file mode 100644
index 0000000000..b49ca46911
--- /dev/null
+++ b/examples/with-fallback-element/src/pages/home.tsx
@@ -0,0 +1,14 @@
+import React from 'react';
+
+export default function Home() {
+ return (
+
+
+ Home Page
+
+
+ Welcome to the Home Page!
+
+
+ );
+}
diff --git a/examples/with-fallback-element/src/pages/index.tsx b/examples/with-fallback-element/src/pages/index.tsx
new file mode 100644
index 0000000000..457352fd03
--- /dev/null
+++ b/examples/with-fallback-element/src/pages/index.tsx
@@ -0,0 +1,15 @@
+import React from 'react';
+import { Link } from 'ice';
+
+export default function Home() {
+ return (
+
+
+ Test Page
+
+
+ Go to Home Page
+
+
+ );
+}
diff --git a/examples/with-fallback-element/src/pages/layout.tsx b/examples/with-fallback-element/src/pages/layout.tsx
new file mode 100644
index 0000000000..444ff1d4b2
--- /dev/null
+++ b/examples/with-fallback-element/src/pages/layout.tsx
@@ -0,0 +1,14 @@
+import React from 'react';
+import { useNavigation, Outlet } from 'ice';
+
+export default function Home() {
+ const navigation = useNavigation();
+ // Use navigation state to determine loading status, and show loading indicator.
+ const isLoading = navigation.state === 'loading';
+ return (
+
+ {isLoading && (
loading
)}
+
+
+ );
+}
diff --git a/packages/runtime/src/ClientRouter.tsx b/packages/runtime/src/ClientRouter.tsx
index b11ac3aa32..d60335a709 100644
--- a/packages/runtime/src/ClientRouter.tsx
+++ b/packages/runtime/src/ClientRouter.tsx
@@ -25,7 +25,7 @@ function createRouterHistory(history: History, router: Router) {
let router: Router = null;
function ClientRouter(props: ClientAppRouterProps) {
const { Component, routerContext } = props;
- const { revalidate } = useAppContext();
+ const { revalidate, appConfig } = useAppContext();
function clearRouter() {
if (router) {
@@ -57,7 +57,8 @@ function ClientRouter(props: ClientAppRouterProps) {
let element: React.ReactNode;
if (process.env.ICE_CORE_ROUTER === 'true') {
- element = ;
+ const fallbackElement = appConfig?.router?.fallbackElement ?? null;
+ element = ;
} else {
element = ;
}
diff --git a/packages/runtime/src/appConfig.ts b/packages/runtime/src/appConfig.ts
index be66c3212c..7b55c43029 100644
--- a/packages/runtime/src/appConfig.ts
+++ b/packages/runtime/src/appConfig.ts
@@ -7,6 +7,7 @@ const defaultAppConfig: AppConfig = {
},
router: {
type: 'browser',
+ fallbackElement: null,
},
};
diff --git a/packages/runtime/src/types.ts b/packages/runtime/src/types.ts
index cd32ee4ab9..23caf34855 100644
--- a/packages/runtime/src/types.ts
+++ b/packages/runtime/src/types.ts
@@ -69,6 +69,7 @@ export interface AppConfig {
type?: 'hash' | 'browser' | 'memory';
basename?: string;
initialEntries?: InitialEntry[];
+ fallbackElement?: React.ReactNode;
};
}
diff --git a/packages/runtime/tests/appConfig.test.ts b/packages/runtime/tests/appConfig.test.ts
index bc3c9daee3..3937fadc74 100644
--- a/packages/runtime/tests/appConfig.test.ts
+++ b/packages/runtime/tests/appConfig.test.ts
@@ -17,6 +17,7 @@ describe('AppConfig', () => {
strict: false,
},
router: {
+ fallbackElement: null,
type: 'browser',
},
});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 115f1ef456..efab792227 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -802,6 +802,28 @@ importers:
specifier: ^18.0.6
version: 18.0.11
+ examples/with-fallback-element:
+ dependencies:
+ '@ice/runtime':
+ specifier: workspace:*
+ version: link:../../packages/runtime
+ react:
+ specifier: ^18.0.0
+ version: 18.2.0
+ react-dom:
+ specifier: ^18.0.0
+ version: 18.2.0(react@18.2.0)
+ devDependencies:
+ '@ice/app':
+ specifier: workspace:*
+ version: link:../../packages/ice
+ '@types/react':
+ specifier: ^18.0.0
+ version: 18.0.34
+ '@types/react-dom':
+ specifier: ^18.0.0
+ version: 18.0.11
+
examples/with-fallback-entry:
dependencies:
'@ice/app':