feat: route transitions (#9275)
Due to nature of server-side rendering, navigation within the admin panel can lead to slow page response times. This can lead to the feeling of an unresponsive app after clicking a link, for example, where the page remains in a stale state while the server is processing. This is especially noticeable on slow networks when navigating to data heavy or process intensive pages. To alleviate the bad UX that this causes, the user needs immediate visual indication that _something_ is taking place. This PR renders a progress bar in the admin panel which is immediately displayed when a user clicks a link, and incrementally grows in size until the new route has loaded in. Inspired by https://github.com/vercel/react-transition-progress. Old: https://github.com/user-attachments/assets/1820dad1-3aea-417f-a61d-52244b12dc8d New: https://github.com/user-attachments/assets/99f4bb82-61d9-4a4c-9bdf-9e379bbafd31 To tie into the progress bar, you'll need to use Payload's new `Link` component instead of the one provided by Next.js: ```diff - import { Link } from 'next/link' + import { Link } from '@payloadcms/ui' ``` Here's an example: ```tsx import { Link } from '@payloadcms/ui' const MyComponent = () => { return ( <Link href="/somewhere"> Go Somewhere </Link> ) } ``` In order to trigger route transitions for a direct router event such as `router.push`, you'll need to wrap your function calls with the `startRouteTransition` method provided by the `useRouteTransition` hook. ```ts 'use client' import React, { useCallback } from 'react' import { useTransition } from '@payloadcms/ui' import { useRouter } from 'next/navigation' const MyComponent: React.FC = () => { const router = useRouter() const { startRouteTransition } = useRouteTransition() const redirectSomewhere = useCallback(() => { startRouteTransition(() => router.push('/somewhere')) }, [startRouteTransition, router]) // ... } ``` In the future [Next.js might provide native support for this](https://github.com/vercel/next.js/discussions/41934#discussioncomment-12077414), and if it does, this implementation can likely be simplified. Of course there are other ways of achieving this, such as with [Suspense](https://react.dev/reference/react/Suspense), but they all come with a different set of caveats. For example with Suspense, you must provide a fallback component. This means that the user might be able to immediately navigate to the new page, which is good, but they'd be presented with a skeleton UI while the other parts of the page stream in. Not necessarily an improvement to UX as there would be multiple loading states with this approach. There are other problems with using Suspense as well. Our default template, for example, contains the app header and sidebar which are not rendered within the root layout. This means that they need to stream in every single time. On fast networks, this would also lead to a noticeable "blink" unless there is some mechanism by which we can detect and defer the fallback from ever rendering in such cases. Might still be worth exploring in the future though.
This commit is contained in:
@@ -1,12 +1,9 @@
|
||||
'use client'
|
||||
|
||||
import { CopyToClipboard, useConfig, useField } from '@payloadcms/ui'
|
||||
import { CopyToClipboard, Link, useConfig, useField } from '@payloadcms/ui'
|
||||
import { formatAdminURL } from '@payloadcms/ui/shared'
|
||||
import LinkImport from 'next/link.js'
|
||||
import React from 'react'
|
||||
|
||||
const Link = (LinkImport.default || LinkImport) as unknown as typeof LinkImport.default
|
||||
|
||||
export const LinkToDocClient: React.FC = () => {
|
||||
const { config } = useConfig()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user