diff --git a/adev/src/content/guide/routing/data-fetching-with-resources.md b/adev/src/content/guide/routing/data-fetching-with-resources.md
new file mode 100644
index 00000000000..9c4b23deac0
--- /dev/null
+++ b/adev/src/content/guide/routing/data-fetching-with-resources.md
@@ -0,0 +1,217 @@
+# Data fetching with resources
+
+The Angular Router integrates with Angular Signals through the `resources` route configuration. This allows you to fetch data reactively using `Resource` APIs.
+
+## Setup
+
+To enable this feature, provide `withRouterResources()` to your router configuration:
+
+```ts
+import {provideRouter, withComponentInputBinding, withRouterResources} from '@angular/router';
+
+bootstrapApplication(App, {
+ providers: [provideRouter(routes, withComponentInputBinding(), withRouterResources())],
+});
+```
+
+You can then define resources in your `Route` definitions and access them directly as component inputs.
+
+The `resources` function runs in an injection context, allowing you to use `inject()` to access services, API clients, or stores directly inside the route definition.
+
+```angular-ts
+import {Component, inject, input, resource} from '@angular/core';
+import {Routes} from '@angular/router';
+import {UserService} from './user.service';
+
+const routes: Routes = [
+ {
+ path: 'user/:id',
+ component: UserProfile,
+ resources: (ctx) => {
+ const userService = inject(UserService);
+ return {
+ user: resource({
+ params: () => ctx.params()['id'],
+ loader: ({params: id}) => userService.getUser(id),
+ }),
+ };
+ },
+ },
+];
+
+@Component({
+ template: `
User: {{ user().name }}
`,
+})
+export class UserProfile {
+ // The router automatically binds only the value for blocking resources.
+ user = input.required();
+}
+```
+
+TIP: Notice we map the exact primitive ID we need in `params: () => ctx.params()['id']`. Passing the entire parameters object (e.g. `params: () => ctx.params()`) can cause unnecessary resource reloads during navigations. Because the router generates a new object identity for the parameters on navigation, the resource will trigger a refetch even if the specific `id` value you care about hasn't changed.
+
+### ResourceContext
+
+The `resources` function receives a `ResourceContext` providing access to route signals (such as `params`, `queryParams`, and `data`) as well as the static `snapshot`.
+
+NOTE: Route resources execute in parallel and cannot access or depend on other resources defined on the route. If you need dependent data fetching, manage that sequence within a single resource's loader.
+
+### Resource implementations and async configuration
+
+The `resources` map supports any Angular `Resource` implementation (such as `resource()`, `rxResource()`, or custom resources).
+
+```ts
+import {Routes} from '@angular/router';
+import {rxResource} from '@angular/core/rxjs-interop';
+
+const routes: Routes = [
+ {
+ path: 'user/:id',
+ component: UserProfile,
+ resources: (ctx) => ({
+ user: rxResource({
+ params: () => ctx.params()['id'],
+ stream: ({params: id}) => fetchUserObservable(id),
+ }),
+ }),
+ },
+];
+```
+
+NOTE: `rxResource` uses the `stream` property instead of `loader` to accept a function that returns an Observable.
+
+The `resources` function can also be `async` and return a `Promise` if you need to perform asynchronous setup or dynamic imports before configuring resources:
+
+```ts
+resources: async (ctx) => {
+ const {fetchUserData} = await import('./user-api');
+ return {
+ user: resource({
+ params: () => ctx.params()['id'],
+ loader: ({params: id}) => fetchUserData(id),
+ }),
+ };
+},
+```
+
+## Accessing resources via ActivatedRoute
+
+When using `withComponentInputBinding()`, blocking resources bind only their unwrapped value directly to component inputs. If you need to interact with the underlying `Resource` instance (for example, to trigger a manual `.reload()` or inspect status signals), you can access it through `ActivatedRoute` or `ActivatedRouteSnapshot`.
+
+```angular-ts
+import {Component, inject, input} from '@angular/core';
+import {ActivatedRoute} from '@angular/router';
+
+@Component({
+ template: `
+
User: {{ user().name }}
+
+ `,
+})
+export class UserProfile {
+ user = input.required();
+ private userResource = inject(ActivatedRoute).resources?.['user'];
+
+ reload() {
+ this.userResource?.reload();
+ }
+}
+```
+
+## Blocking and non-blocking resources
+
+By default, all resources returned from `resources` are **blocking**. The router waits until the data is fully loaded before activating the route and component.
+
+**For blocking resources, the router binds only the resolved value to the component input.** The input type in your component is `T` instead of `Resource`.
+
+This simplifies your component because it does not need to handle loading or error states. Because the router blocks navigation until the resource is loaded, the component never observes a `loading` state. If the resource throws an error, the router cancels the navigation, so the component never observes an `error` state.
+
+If you prefer to handle loading states in the UI, use the `nonBlocking()` wrapper utility. Non-blocking resources do not halt navigation. The router activates the component immediately, allowing the UI to handle loading or skeleton states.
+
+**For non-blocking resources, the router binds the full `Resource` object to the component input.** This allows you to access `.isLoading()`, `.error()`, and other resource signals in your component.
+
+```angular-ts
+import {Component, input, Resource, resource} from '@angular/core';
+import {Routes, nonBlocking} from '@angular/router';
+
+const routes: Routes = [
+ {
+ path: 'reports',
+ component: Reports,
+ resources: (ctx) => ({
+ reportData: nonBlocking(
+ resource({
+ loader: () => fetchHeavyReportData(),
+ }),
+ ),
+ }),
+ },
+];
+
+@Component({
+ template: `
+ @if (reportData().isLoading()) {
+
Loading...
+ } @else if (reportData().error()) {
+
Error loading report.
+ } @else if (reportData().hasValue()) {
+
+ }
+ `,
+})
+export class Reports {
+ reportData = input.required>();
+}
+```
+
+NOTE: If a blocking resource throws an error, the router cancels the navigation and emits a `NavigationError` event. Resources wrapped in `nonBlocking()` that error will complete navigation and expose the error via the `resource.error()` signal.
+
+### Redirecting from a resource
+
+If a blocking resource needs to redirect the user (for example, if an item is not found), throw a `RedirectCommand` inside the resource loader. The router will cancel the current navigation and redirect to the specified URL:
+
+```ts
+import {inject, resource} from '@angular/core';
+import {RedirectCommand, Router, Routes} from '@angular/router';
+
+const routes: Routes = [
+ {
+ path: 'user/:id',
+ component: UserProfile,
+ resources: (ctx) => {
+ const router = inject(Router);
+
+ return {
+ user: resource({
+ params: () => ctx.params()['id'],
+ loader: async ({params: id}) => {
+ const user = await fetchUser(id);
+ if (!user) {
+ throw new RedirectCommand(router.parseUrl('/not-found'));
+ }
+ return user;
+ },
+ }),
+ };
+ },
+ },
+];
+```
+
+## Transitional states during pending navigations
+
+When moving between views (or reloading the same view with new parameters), switching abruptly to a loading skeleton can create a jarring UI flash.
+
+The router automatically masks intermediate `loading` and `reloading` states of resolved resources while a navigation is pending.
+
+If you navigate from `/user/1` to `/user/2`, `UserProfile` stays mounted and continues rendering data from `/user/1` (frozen in its exact state) until `/user/2` resolves. Once `/user/2` settles, the router unfreezes the UI, transitioning directly to the new data with no loading flash.
+
+NOTE: Route resources returned to the router are read-only. Manual `.reload()` calls attempted during an active navigation transition or rollback recovery return `false` to avoid interrupting router transition tracking.
+
+### Rollback recovery on cancellation
+
+If a navigation is cancelled (for example, by a guard), the router reverts the state tree to the previous state. This reversion can cause the resource's signal dependencies (such as route parameters) to revert to their previous values.
+
+Because the parameters changed back, the resource might automatically trigger a new load to fetch data for the old parameters. To prevent flashing a loading state for data that was already visible, the router retains the previous resource snapshot in the UI until the resource has settled in the reverted state.
+
+TIP: Forward the `abortSignal` provided by the resource loader to your asynchronous calls (like `fetch`). When the router rolls back parameters or supersedes navigations, the pending request is cleanly aborted: `loader: ({params: id, abortSignal}) => fetchUser(id, {signal: abortSignal})`.
diff --git a/packages/router/test/router_resource_spec.ts b/packages/router/test/router_resource_spec.ts
index b2d30781ead..3d0e7e3c6d1 100644
--- a/packages/router/test/router_resource_spec.ts
+++ b/packages/router/test/router_resource_spec.ts
@@ -774,6 +774,30 @@ describe('Router resources integration', () => {
expect(handleCount).toBe(1);
expect((errorRef as Error).message).toBe('Resource failed!');
});
+
+ it('should redirect when a blocking resource throws a RedirectCommand', async () => {
+ const {harness, router} = await setupRouter([
+ {
+ path: 'test',
+ component: TargetCmp,
+ resources: () => ({
+ data: resource({
+ loader: async () => {
+ throw new RedirectCommand(TestBed.inject(Router).parseUrl('/redirected'));
+ },
+ }),
+ }),
+ },
+ {
+ path: 'redirected',
+ component: TargetCmp,
+ },
+ ]);
+
+ await harness.navigateByUrl('/test');
+
+ expect(router.url).toBe('/redirected');
+ });
});
describe('rxResource Integration', () => {