Skip to content

Commit

Permalink
[NextJS] Add prefetchLinks parameter to RichText component (#1517)
Browse files Browse the repository at this point in the history
* Fix internal link issue in NextJs RichText
Subsequent pages don't have client side routing

Added a useEffect dependancy to the NextJs RichText component. This
fixes an issue where where the useEffect() hook doesn't run when
navigating to a page that contains a RichText component after clicking an
internal link in a RichText component on another page to cause the
navigation.

Description:
1. Open a page that contains a RichText component that has an <a> tag
   that links to a page within the same site (i.e. clicking the link
   fires the routeHandler() function within the richText component that
   performs a client side navigation)

2. When the RichText component on the new page renders, it doesn't run
   the useEffect hook so the event handler doesn't get attached to any
   internal links within the RichText component

3. Clicking on any of the embedded links causes a whole page refresh as
   the event handler isn't attached, so no client side routing occurs.

* Make link prefetching configurable in RichText

Add 'prefetchLinks' parameter to NextJs RichText component to allow
users to turn prefetching of internal links on and off.

This can reduce the amount of data fetch, particularly on a page with
many links that are unlikely to be visited.

* Added unit test for preftechLinks in RichText component

* Updated chanelog for prefetchLinks parameter in NextJS RichText component
  • Loading branch information
pschofield authored Jun 9, 2023
1 parent dfb380c commit 9a2c6b2
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 7 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Our versioning strategy is as follows:
* `[templates/nextjs-personalize]` `[sitecore-jss]` Update the default personalize middleware, personalize/cdp service timeout values to 400 ([#1507](https://github.com/Sitecore/jss/pull/1507))
* `[templates/react]` `[templates/angular]` `[templates/vue]` Remove persisted query link since APQ(Automatic Persisted Queries) is not supported on Sitecore Experience Edge Delivery ([#1420](https://github.com/Sitecore/jss/pull/1512))
* `[sitecore-jss]` `[templates/nextjs-personalize]` Introduced optional personalize scope identifier to isolate embedded personalization data among XM Cloud Environments that are sharing a Personalize tenant ([#1494](https://github.com/Sitecore/jss/pull/1494))
* `[sitecore-jss-nextjs]` Add prefetchLinks paramter to the RichText component to allow prefetching of links to be enabled/disabled ([#1517](https://github.com/Sitecore/jss/pull/1517))

### 🧹 Chores

Expand Down
29 changes: 29 additions & 0 deletions packages/sitecore-jss-nextjs/src/components/RichText.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -329,4 +329,33 @@ describe('RichText', () => {

expect(c.find(ReactRichText).length).to.equal(1);
});

it('Should not call prefetch when prefetchLinks is set to false', () => {
const app = document.createElement('main');

document.body.appendChild(app);

const router = Router();

const props = {
field: {
value:
'<div id="test"><h1>Prefetch test!</h1><a href="/notprefetched1">1</a><a href="/notprefetched2">2</a></div>',
},
};

const c = mount(
<Page value={router}>
<RichText {...props} prefetchLinks={false} />
</Page>,
{ attachTo: app }
);

expect(c.html()).contains('<div id="test">');
expect(c.html()).contains('<h1>Prefetch test!</h1>');
expect(c.html()).contains('<a href="/notprefetched1">1</a>');
expect(c.html()).contains('<a href="/notprefetched2">2</a>');

expect(router.prefetch).callCount(0);
});
});
22 changes: 15 additions & 7 deletions packages/sitecore-jss-nextjs/src/components/RichText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,19 @@ export type RichTextProps = ReactRichTextProps & {
* @default 'a[href^="/"]'
*/
internalLinksSelector?: string;

/**
* Controls the prefetch of internal links. This can be beneficial if you have RichText fields
* with large numbers of internal links in them.
* @default true
*/
prefetchLinks?: boolean;
};

const prefetched: { [cacheKey: string]: boolean } = {};

export const RichText = (props: RichTextProps): JSX.Element => {
const { internalLinksSelector = 'a[href^="/"]', ...rest } = props;
const { internalLinksSelector = 'a[href^="/"]', prefetchLinks = true, ...rest } = props;
const hasText = props.field && props.field.value;
const isEditing = props.editable && props.field && props.field.editable;

Expand Down Expand Up @@ -51,13 +58,14 @@ export const RichText = (props: RichTextProps): JSX.Element => {
if (!internalLinks || !internalLinks.length) return;

internalLinks.forEach((link) => {
if (link.target !== '_blank') {
if (!prefetched[link.pathname]) {
router.prefetch(link.pathname, undefined, { locale: false });
prefetched[link.pathname] = true;
}
link.addEventListener('click', routeHandler, false);
if (link.target === '_blank') return;

if (prefetchLinks && !prefetched[link.pathname]) {
router.prefetch(link.pathname, undefined, { locale: false });
prefetched[link.pathname] = true;
}

link.addEventListener('click', routeHandler, false);
});
};

Expand Down

0 comments on commit 9a2c6b2

Please sign in to comment.