Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement internal and external redirects using PageMetadata from Contentful. #557

Merged
merged 12 commits into from
Aug 14, 2024

Conversation

LouisFettet
Copy link
Member

@LouisFettet LouisFettet commented May 30, 2024

We had originally built the Page details content type with support for internal redirects (a reference to another Page details entry) and external redirects (a URL string), but we never implemented the feature on the site.

Proposed changes

  • Split our query for the pageMetadataMap based on the need for redirects. Change our default parameters for loadPageMetadataMap so that we assume we don't need breadcrumbs or redirects. Include the data for redirects in the query, if available.
  • In our base layout server file (src/routes/+layout.server.ts), we attempt to find a matching Page details entry for the requested path, check if that entry has any internal or external redirects, and throw an error using the built-in SvelteKit redirect method.
  • In Contentful, update the internal redirect field on Page details so that (in addition to another Page details entry) it will also accept a News or Event entry. Support all three types of internal redirects in the base layout server file.

Acceptance criteria validation

To test this out locally, switch your DEFAULT_CONTENTFUL_ENVIRONMENT in your .env.local config file from master to redirect-dev (I set up this env in Contentful to avoid causing any problems on production).

  • Navigating to /indian-creek-recreation-area/book-your-stay-today externally redirects to the ResNexus site
  • Navigating to /indian-creek-recreation-area/getting-to-indian-creek internally redirects to /indian-creek-recreation-area/activities-amenities.
  • Navigating to /online-payments internally redirects to /business/online-payments.
    • Page details entry: Horticulture Online Payments (This entry was already set up with an internal redirect, so I just set the parent page to the homepage for testing.)
  • Navigating to /indian-creek-recreation-area/campground-maps internally redirects to /about/events/event/2024-05-29-louisiana-agricultural-commodities-commission-meeting.
  • Navigating to /indian-creek-recreation-area/pavilion-reservations internally redirects to /about/news/article/ag-commissioner-mike-strain-in-cuba-building-bridges-for-la-export.
  • Pages that redirect do not appear in the mega menu or the side nav.

Other details

Alternate solutions

While it might have been nice to handle this in a hook instead, I wanted to keep things relatively simple and add the logic after we load the pageMetadataMap (I'm honestly not sure how this would look in a hook unless we built the map in the hook, which doesn't seem right.).

I originally added some of this logic in the individual +page.server.ts files for the [topTierPage] and [...serviceGroupPage] routes, but this was repetitive, and I think handling it in the base layout is cleaner. We are doing a bit of double work now that we're looking up the Page details entry via the map twice (once in the base layout to determine a redirect and once again when loading Top Tier and Core Content pages), so this could probably still be improved.

Possible drawbacks

This setup allows for redirecting from any path (both those that already exist and ones that don't exist yet), but the editor experience for redirecting from a non-existent path (especially a nested one) is a bit weird. For instance, if you wanted to redirect from a new nested path /abc/xyz to /, you would need to set up entries for both abc and xyz and add the relevant redirect on xyz. If abc is just there for the URL, you will need to add a redirect on abc as well so that it doesn't show up as a weird error page.

Additionally, there doesn't seem to be an easy way to prevent an infinite redirect loop; if two pages redirect to each other, the site will likely break pretty badly. We could add some basic protection against this in the base layout server file, but it would decrease efficiency and still be relatively difficult to handle complex loop situations (e.g. /a redirects to /b, /b redirects to /c, and /c redirects to /a). Rather than trying to protect against this edge case, it's probably wiser to alert editors to this edge case in the user guide.

Requested feedback

I'll leave a self-review, but I'm rusty, so any and all feedback here is appreciated!

To-Do

Some things we should probably think through / update:

  • Figure out how to handle paths with redirects in the sitemap and search.
    • Opted to filter out paths with redirects from the sitemap and Algolia search index. I think this is the right call, as these search results likely wouldn't be useful. We can ask LDAF folks if they feel differently before we deploy.
  • See about cutting down on repetition in querying the pageMetadataMap in both the base layout and individual page server files (it's only loaded from Contentful once, but the extra lookups are wasteful).
    • Opted not to handle this here, but left a TODO comment in the base layout server file.
  • Figure out how to handle internal redirects to pages without Page details entries such as news and events.

Before merging / deploying, we'll want to:

  • Add News and Events as supported entry types for internal redirects on Page Details.
  • Delete the redirect-dev environment.
  • Write up a quick how-to guide for adding redirects. Should include how to set up internal and external redirects for pages that do and don't exist (with additional instructions for nested paths), and in all cases should account for the weirdness around News and Events not having Page Details associated with them.
  • Check with LDAF folks that things are set up the way they want (e.g. see if they want /online-payments to internally redirect to /business/online-payments and /indian-creek-recreation-area/book-your-stay-today to externally redirect to ResNexus).
  • Publish the Page Details entries for the intermediary slugs under News and Events:

Copy link

vercel bot commented May 30, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
ldaf ✅ Ready (Inspect) Visit Preview 💬 Add feedback Aug 14, 2024 3:00pm

Copy link
Member Author

@LouisFettet LouisFettet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quick initial self-review.

src/lib/loadPageMetadataMap.ts Outdated Show resolved Hide resolved
pageMetadata.internalRedirect.sys.id,
);
if (internalRedirectPageMetadata?.url) {
throw redirect(301, internalRedirectPageMetadata.url);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

301 felt like the right option here for the ones available, but let me know if you think there's a better option.

Copy link
Contributor

@hinzed1127 hinzed1127 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a totally viable solution. The only alternative approach I can immediately think of is having a separate metadata query that's much slimmer and only grabs redirects. Or in a similar vein, just making the includeRedirects and includeBreadcrumbs named arguments default to false in the function definition, as to avoid accruing too much bloat in the function arguments, but that concern might still be a little premature.

Base automatically changed from louis/replace-ts-node-with-tsx to main June 11, 2024 15:56
@LouisFettet
Copy link
Member Author

Got redirects to News and Events pages mostly working. Now noticing that my workaround to redirect from a News page has added a bunch of junk to the sidebar, which we don't want. Will look into this a bit more tomorrow.

…ge default behavior to not needing redirects and breadcrumbs.
id
}
}
internalRedirect {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RE: @hinzed1127 separating the query so we only grab redirects, I think that would have been less efficient since we would be making double the requests to Contentful (when we could just grab it all in one go while we're working on loading the PageMetadataMap).

That said, I wanted to create a fragment so we could have two separate queries (one for all the entries and one for only entries that don't include redirects), but in both cases here we're still asking for data on internalRedirect and externalRedirect even though in one of those cases we know that these will all come back null. Unsure how to clean this up so that we don't query for values we know are null without removing the fragment and duplicating the majority of the query.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unsure how to clean this up so that we don't query for values we know are null...

Not sure if I follow this bit. What scenario would do that?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fragment here is querying for internalRedirect and externalRedirect, but we use the fragment for both types of queries:

  1. queryAll - we do this when includeRedirects is true and therefore we want the data on internalRedirect and externalRedirect
  2. queryWithoutRedirects - we do this when includeRedirects is false and we apply a filter:
    where: { AND: [{ internalRedirect_exists: false }, { externalRedirect_exists: false }] }
    
    But because we're reusing the same fragment we'll still get back internalRedirect and externalRedirect in the response (all of which will be null based on the filter).

Ideally we wouldn't query for internalRedirect and externalRedirect in the second case, but I don't think there's a way to do that without duplicating the GraphQL query?

Comment on lines +139 to +140
includeBreadcrumbs = false,
includeRedirects = false,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed these to default to false per your suggestion @hinzed1127. Even though the main use-case includes these, we have more use-cases where we don't.

@LouisFettet LouisFettet marked this pull request as ready for review June 17, 2024 21:17
@LouisFettet LouisFettet requested a review from hinzed1127 June 17, 2024 21:17
Copy link
Contributor

@hinzed1127 hinzed1127 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Looks good! And honestly the end result is pretty straightforward, just the small wrinkle to account for news & events and press releases separately

Copy link

Coverage after merging louis/redirects into main will be

94.95%

Coverage Report
FileStmtsBranchesFuncsLinesUncovered Lines
src/hooks/server
   handleCurrentUserAndContentfulClient.ts88.94%78.13%100%90.80%132–141, 150–151, 52–53, 57, 57, 73–75, 94–96
src/lib/actions
   logout.ts48.78%0%0%51.28%18–37
src/lib/components/Accordion
   Accordion.svelte100%100%100%100%
   AccordionItem.svelte94.12%55.56%100%100%23, 33, 46, 48
   context.ts100%100%100%100%
   index.ts100%100%100%100%
src/lib/components/Alert
   Alert.svelte94.74%57.14%100%100%34, 41, 41
src/lib/components/AnnouncementBanner
   AnnouncementBanner.svelte100%100%100%100%
src/lib/components/Breadcrumbs
   Breadcrumbs.svelte100%100%100%100%
src/lib/components/Button
   Button.svelte96.77%50%100%100%41, 50
   buttonOptions.ts100%100%100%100%
   index.ts100%100%100%100%
src/lib/components/Card
   Card.svelte97.50%0%100%100%7
   CardGroup.svelte100%100%100%100%
   index.ts100%100%100%100%
src/lib/components/ConditionalWrapper
   ConditionalWrapper.svelte100%100%100%100%
   index.ts100%100%100%100%
src/lib/components/ContactCard
   ContactCard.svelte98.91%93.75%100%100%54
   Section.svelte100%100%100%100%
   index.ts100%100%100%100%
src/lib/components/ContentfulRichText
   ContentfulRichText.svelte97.87%0%100%100%25
   context.ts98%80%100%100%8
   headings.ts93.18%50%100%95.12%37–39
   predicates.ts90.76%100%70%94.12%68, 76, 79, 82, 85
src/lib/components/ContentfulRichText/nodes
   AssetHyperlink.svelte100%100%100%100%
   Blockquote.svelte100%100%100%100%
   EmbeddedAssetBlock.svelte87.50%0%100%100%12, 16, 20, 29, 36–38
   EmbeddedEntry.svelte100%100%100%100%
   EmbeddedEntryBlock.svelte100%100%100%100%
   EntryHyperlink.svelte100%100%100%100%
   Heading1.svelte100%100%100%100%
   Heading2.svelte100%100%100%100%
   Heading3.svelte100%100%100%100%
   Heading4.svelte100%100%100%100%
   Heading5.svelte100%100%100%100%
   Heading6.svelte100%100%100%100%
   Hr.svelte100%100%100%100%
   Hyperlink.svelte100%100%100%100%
   ListItem.svelte100%100%100%100%
   Node.svelte93.33%0%100%100%7
   OrderedList.svelte100%100%100%100%
   Paragraph.svelte100%100%100%100%
   Table.svelte100%100%100%100%
   TableCell.svelte100%100%100%100%
   TableHeaderCell.svelte100%100%100%100%
   TableRow.svelte100%100%100%100%
   Text.svelte100%100%100%100%
   UnorderedList.svelte100%100%100%100%
   index.ts100%100%100%100%
src/lib/components/CopyToClipboard
   CopyToClipboard.svelte97.50%60%100%100%71, 71
   index.ts100%100%100%100%
src/lib/components/Footer
   Footer.svelte95.34%55%100%100%105, 105, 108, 108, 38, 63, 69, 69, 75
src/lib/components/Header
   Header.svelte94.44%50%100%100%47, 47–48, 48, 51
src/lib/components/Header/Logo
   Logo.svelte100%100%100%100%
   index.ts100%100%100%100%
src/lib/components/Header/Nav
   Nav.svelte97.06%83.33%100%100%19
   NavItem.svelte100%100%100%100%
   NavLink.svelte100%100%100%100%
   NavMenu.svelte96.40%70.59%100%100%100, 77, 79, 88, 98
   index.ts100%100%100%100%
src/lib/components/Header/Title
   Title.svelte100%100%100%100%
   index.ts100%100%100%100%
src/lib/components/Header/User
   User.svelte94.74%0%100%100%10
   index.ts100%100%100%100%
src/lib/components/Header/__mocks__
   HeaderBackgroundImage.svelte100%100%100%100%
src/lib/components/Icon
   Icon.svelte87.50%20%100%100%19, 19, 24, 24
   index.ts100%100%100%100%
src/lib/components/Image
   BlurhashRenderer.svelte100%100%100%100%
   Image.svelte90.86%67.74%66.67%96.35%115, 149, 165, 167–168, 201, 206, 211–215, 241–245, 245, 267–268, 33, 35, 38, 50, 61, 61–62, 72–74
   index.ts100%100%100%100%
   renderBlurhash.ts100%100%100%100%
src/lib/components/IntersectionObserver
   IntersectionObserver.svelte94.74%75%100%100%36–37, 39
   RootIntersectionObserver.svelte85.19%0%100%92%14–17
   index.ts100%100%100%100%
   key.ts100%100%100%100%
   observe.ts99.03%93.33%100%100%73
src/lib/components/Link
   Link.svelte100%100%100%100%
   index.ts100%100%100%100%
src/lib/components/LogoutLink
   LogoutLink.svelte100%100%100%100%
   index.ts100%100%100%100%
src/lib/components/PreviewToggle
   PreviewToggle.svelte100%100%100%100%
   index.ts100%100%100%100%
src/lib/components/Search
   Search.svelte91.30%33.33%100%96.77%37–39, 44, 44, 48
   index.ts100%100%100%100%
   options.ts100%100%100%100%
src/lib/components/SideNav
   SideNav.svelte100%100%100%100%
   SideNavItem.svelte100%100%100%100%
src/lib/components/Tag
   Tag.svelte100%100%100%100%
   index.ts100%100%100%100%
src/lib/components/VideoCard
   VideoCard.svelte91.30%0%0%94.23%103–112, 53, 78
   getTrimmedFirstParagraph.ts100%100%100%100%
   index.ts100%100%100%100%
src/lib/components/YoutubeSubscribeLink
   YoutubeSubscribeLink.svelte100%100%100%100%
   index.ts100%100%100%100%
src/lib/constants
   date.ts100%100%100%100%
   images.ts100%100%100%100%
   support.ts43.75%0%100%50%12–14, 4–9
   tokenDuration.ts100%100%100%100%
src/lib/context
   currentUser.ts88.89%100%50%93.33%14
   pageMetadataMap.ts100%100%100%100%
src/lib/imageServices
   contentful.ts97.06%86.67%100%100%21, 48
src/lib/services/blurhashes
   index.ts50%100%0%52.94%22–38, 49–51, 6–9
src/lib/services/server/contentful
   graphqlClient.ts91%75%100%92.13%71–78, 81
   index.ts100%100%100%100%
src/lib/services/server/contentfulManagement
   getCurrentUser.ts98.08%66.67%100%100%25
   index.ts100%100%100%100%
src/lib/services/server/kv/__mocks__
   index.ts93.55%100%100%92.86%21, 25
src/lib/services/server/youtube
   getYoutubeVideoData.ts74.63%16.67%50%81.36%33–34, 38, 40, 44, 49–59
src/lib/util
   classNames.ts100%100%100%100%
   dates.ts78.57%100%60%79.41%14–17, 32–34
   getErrorMessage.ts63.16%20%100%76.92%10–11, 2–3, 7–9
   getErrorMessageFromResponse.ts20%25%20%19.72%12–19, 2, 20, 23–29, 3, 30–45, 50–59, 6, 60–69, 7, 70–71, 8–9
   getYoutubeVideoIDFromURL.ts66.67%33.33%100%80%3, 3–4
   resolveWithStatus.server.ts100%100%100%100%
   slugify.ts100%100%100%100%
   warn.ts100%100%100%100%
src/routes/login
   +page.server.ts89.71%70%100%92.98%32–35, 49, 52–53
src/routes/logout
   +page.server.ts81.82%75%100%82.35%11–14
src/stories
   AccordionView.svelte100%100%100%100%
   CardView.svelte99.10%50%100%100%75

Copy link

@LouisFettet LouisFettet merged commit 14e5596 into main Aug 14, 2024
8 checks passed
@LouisFettet LouisFettet deleted the louis/redirects branch August 14, 2024 15:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants