Skip to content

Commit 3d48b24

Browse files
authored
feat: remove page-level loaders from AppShellPageProps (#334)
* feat!: remove page loaders from AppShellPageProps * docs: shorten changeset summary * docs: add reason to changeset * docs: downgrade changeset bump to patch
1 parent 761da47 commit 3d48b24

12 files changed

Lines changed: 59 additions & 54 deletions

File tree

.changeset/calm-geckos-drop.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@tailor-platform/app-shell": patch
3+
"@tailor-platform/app-shell-vite-plugin": patch
4+
---
5+
6+
Remove `loader` from file-based page definitions (`Page.appShellPageProps`). This removes an accidentally exposed incomplete API and makes `guards` the single source of page-level route behavior in file-based routing.

docs/api/define-module.md

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,12 +85,6 @@ See [Guards Overview](./guards/overview.md) for details.
8585
- **Required:** No
8686
- **Description:** Error boundary component for this module and its child resources. Use `useRouteError` hook to access error details.
8787

88-
### `loader`
89-
90-
- **Type:** `(args: LoaderFunctionArgs) => Promise<unknown> | unknown`
91-
- **Required:** No
92-
- **Description:** React Router loader function for data fetching
93-
9488
## Return Type
9589

9690
```typescript

docs/api/define-resource.md

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,6 @@ See [Guards Overview](./guards/overview.md) for details.
8686
- **Required:** No
8787
- **Description:** Error boundary component for this resource
8888

89-
### `loader`
90-
91-
- **Type:** `(args: LoaderFunctionArgs) => Promise<unknown> | unknown`
92-
- **Required:** No
93-
- **Description:** React Router loader function
94-
9589
## Return Type
9690

9791
```typescript
@@ -184,19 +178,6 @@ const adminSettingsResource = defineResource({
184178
});
185179
```
186180

187-
### Resource with Loader
188-
189-
```typescript
190-
const productResource = defineResource({
191-
path: ":id",
192-
component: ProductPage,
193-
loader: async ({ params }) => {
194-
const product = await fetch(`/api/products/${params.id}`);
195-
return product.json();
196-
},
197-
});
198-
```
199-
200181
### Resource with Error Boundary
201182

202183
```typescript

docs/concepts/file-based-routing.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,6 @@ type AppShellPageProps = {
133133
breadcrumbTitle?: string | ((segment: string) => string);
134134
};
135135
guards?: Guard[];
136-
loader?: LoaderHandler;
137136
};
138137
```
139138

packages/core/skills/app-shell-patterns/references/fundamental/components.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -605,7 +605,7 @@ Types for authoring guard functions used by `WithGuard` and `appShellPageProps.g
605605

606606
### `RouteParams`, `PageComponent`, `PageMeta`, `AppShellPageProps`, `AppShellRegister`, `ContextData` (types)
607607

608-
Types for declaring page props (`appShellPageProps = { meta, guards, loader }`), reading typed route params, and registering route types globally. See `project-setup.md` for usage.
608+
Types for declaring page props (`appShellPageProps = { meta, guards }`), reading typed route params, and registering route types globally. See `project-setup.md` for usage.
609609

610610
---
611611

packages/core/skills/app-shell-patterns/references/fundamental/graphql.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ Every page component:
191191
};
192192
```
193193

194-
3. Uses `appShellPageProps.guards` for permission gates and `appShellPageProps.loader` for route loaders. See [project-setup.md](project-setup.md).
194+
3. Uses `appShellPageProps.guards` for permission gates. See [project-setup.md](project-setup.md).
195195

196196
## Quick reference
197197

packages/core/src/fs-routes/converter.test.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const redirectGuard = async () => ({
1818
type: "redirect" as const,
1919
to: "/dashboard",
2020
});
21+
const explicitLoader = async () => ({ ok: true });
2122

2223
const createMockPage = (
2324
path: string,
@@ -270,6 +271,32 @@ describe("convertPagesToModules", () => {
270271
expect(modules[0].guards).toHaveLength(0);
271272
expect(modules[0].loader).toBeUndefined();
272273
});
274+
275+
it("ignores appShellPageProps.loader on modules", () => {
276+
const pages = [
277+
createMockPage("/dashboard", {
278+
meta: { title: "Dashboard" },
279+
// @ts-expect-error breaking change: page loaders are no longer supported
280+
loader: explicitLoader,
281+
}),
282+
];
283+
const modules = convertPagesToModules(pages);
284+
285+
expect(modules[0].loader).toBeUndefined();
286+
});
287+
288+
it("ignores appShellPageProps.loader on resources", () => {
289+
const pages = [
290+
createMockPage("/dashboard"),
291+
createMockPage("/dashboard/orders", {
292+
// @ts-expect-error breaking change: page loaders are no longer supported
293+
loader: explicitLoader,
294+
}),
295+
];
296+
const modules = convertPagesToModules(pages);
297+
298+
expect(modules[0].resources[0].loader).toBeUndefined();
299+
});
273300
});
274301

275302
// ============================================

packages/core/src/fs-routes/converter.tsx

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,7 @@ function nodeToResource(node: PageNode): Resource {
7979
const title = getTitle(Component, node.path);
8080
const icon = Component?.appShellPageProps?.meta?.icon;
8181
const breadcrumbTitle = Component?.appShellPageProps?.meta?.breadcrumbTitle;
82-
const explicitLoader = Component?.appShellPageProps?.loader;
83-
const loader =
84-
explicitLoader ??
85-
(node.guards && node.guards.length > 0 ? withGuardsLoader(node.guards) : undefined);
82+
const loader = node.guards && node.guards.length > 0 ? withGuardsLoader(node.guards) : undefined;
8683

8784
// Recursively convert children to subResources
8885
const subResources: Resource[] = [];
@@ -116,10 +113,7 @@ function nodeToModule(node: PageNode): Module {
116113
const title = getTitle(Component, node.path);
117114
const icon = Component?.appShellPageProps?.meta?.icon;
118115
const breadcrumbTitle = Component?.appShellPageProps?.meta?.breadcrumbTitle;
119-
const explicitLoader = Component?.appShellPageProps?.loader;
120-
const loader =
121-
explicitLoader ??
122-
(node.guards && node.guards.length > 0 ? withGuardsLoader(node.guards) : undefined);
116+
const loader = node.guards && node.guards.length > 0 ? withGuardsLoader(node.guards) : undefined;
123117

124118
// Convert children to resources
125119
const resources: Resource[] = [];

packages/core/src/fs-routes/types.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { FC, ReactNode } from "react";
2-
import type { Guard, LoaderHandler } from "@/resource";
2+
import type { Guard } from "@/resource";
33
import type { LocalizedString } from "@/lib/i18n";
44

55
// ============================================
@@ -52,18 +52,13 @@ export type AppShellPageProps = {
5252
* ```
5353
*/
5454
guards?: Guard[];
55-
56-
/**
57-
* Loader function to fetch data before rendering the page.
58-
*/
59-
loader?: LoaderHandler;
6055
};
6156

6257
/**
6358
* A React component that can be used as a page in file-based routing.
6459
*
6560
* The component can optionally have an `appShellPageProps` static field
66-
* for configuring navigation metadata, guards, and loaders.
61+
* for configuring navigation metadata and guards.
6762
*/
6863
export type PageComponent = FC & {
6964
appShellPageProps?: AppShellPageProps;

packages/vite-plugin/src/schema.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import { s } from "./validator";
1212
* DashboardPage.appShellPageProps = {
1313
* meta: { title: "Dashboard", icon: <DashboardIcon /> },
1414
* guards: [authGuard],
15-
* loader: async () => ({ data: "..." }),
1615
* } satisfies AppShellPageProps;
1716
* ```
1817
*/
@@ -43,9 +42,4 @@ export const appShellPagePropsSchema = s.object({
4342
* Guards are executed in order. Each page evaluates only its own guards (no inheritance from parent pages).
4443
*/
4544
guards: s.array(s.any()).optional(),
46-
47-
/**
48-
* Loader function to fetch data before rendering the page.
49-
*/
50-
loader: s.any().optional(),
5145
});

0 commit comments

Comments
 (0)