-
Notifications
You must be signed in to change notification settings - Fork 22
NEX-179: Move the redirect logic to middleware #290
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
import { Metadata } from "next"; | ||
import { draftMode } from "next/headers"; | ||
import { notFound, permanentRedirect, redirect } from "next/navigation"; | ||
import { notFound } from "next/navigation"; | ||
import { getDraftData } from "next-drupal/draft"; | ||
import { setRequestLocale } from "next-intl/server"; | ||
|
||
|
@@ -9,10 +9,7 @@ import { REVALIDATE_LONG } from "@/lib/constants"; | |
import { getNodeByPathQuery } from "@/lib/drupal/get-node"; | ||
import { getNodeMetadata } from "@/lib/drupal/get-node-metadata"; | ||
import { getNodeStaticParams } from "@/lib/drupal/get-node-static-params"; | ||
import { | ||
extractEntityFromRouteQueryResult, | ||
extractRedirectFromRouteQueryResult, | ||
} from "@/lib/graphql/utils"; | ||
import { extractEntityFromRouteQueryResult } from "@/lib/graphql/utils"; | ||
|
||
type NodePageParams = { | ||
params: { slug: string[]; locale: string }; | ||
|
@@ -55,20 +52,6 @@ export default async function NodePage({ | |
// in the getNodeByPathQuery function. | ||
const nodeByPathResult = await getNodeByPathQuery(path, locale, isDraftMode); | ||
|
||
// The response will contain either a redirect or node data. | ||
// If it's a redirect, redirect to the new path: | ||
const redirectResult = extractRedirectFromRouteQueryResult(nodeByPathResult); | ||
|
||
if (redirectResult) { | ||
// Set to temporary redirect for 302 and 307 status codes, | ||
// and permanent for all others. | ||
if (redirectResult.status === 307 || redirectResult.status === 302) { | ||
redirect(redirectResult.url); | ||
} else { | ||
permanentRedirect(redirectResult.url); | ||
} | ||
} | ||
|
||
// Extract the node entity from the query result: | ||
let node = extractEntityFromRouteQueryResult(nodeByPathResult); | ||
|
||
|
@@ -77,11 +60,6 @@ export default async function NodePage({ | |
notFound(); | ||
} | ||
|
||
// If the node is a frontpage, redirect to the frontpage: | ||
if (!isDraftMode && node.__typename === "NodeFrontpage") { | ||
redirect(`/${locale}`); | ||
} | ||
|
||
Comment on lines
-80
to
-84
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ah, also this we would need to handle in middleware maybe |
||
// When in draftMode, we could be requesting a specific revision. | ||
// In this case, the draftData will contain the resourceVersion property, | ||
// which we can use to fetch the correct revision: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I had to move this function to its own file, because we could not import an export from this file in middleware (the neshka/cache package causes webpack errors there) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { | ||
drupalClientPreviewer, | ||
drupalClientViewer, | ||
} from "@/lib/drupal/drupal-client"; | ||
import { GET_ENTITY_AT_DRUPAL_PATH } from "@/lib/graphql/queries"; | ||
|
||
/** | ||
* Function to directly fetch a node from Drupal by its path and locale. | ||
* | ||
* This function is used to fetch the node data without caching, and | ||
* can be imported in middleware or other server-side code. | ||
* | ||
* @param path The path of the node. | ||
* @param locale The locale of the node. | ||
* @param revision The revision of the node. | ||
* @param isDraftMode If true, fetches the draft version of the node. | ||
* @returns The fetched node data or null if not found. | ||
*/ | ||
export async function fetchNodeByPathQuery( | ||
path: string, | ||
locale: string, | ||
isDraftMode: boolean, | ||
revision: string = null, | ||
) { | ||
const drupalClient = isDraftMode ? drupalClientPreviewer : drupalClientViewer; | ||
return await drupalClient.doGraphQlRequest(GET_ENTITY_AT_DRUPAL_PATH, { | ||
path, | ||
langcode: locale, | ||
revision, | ||
}); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,36 +3,10 @@ import { neshCache } from "@neshca/cache-handler/functions"; | |
import { AbortError } from "p-retry"; | ||
|
||
import { REVALIDATE_LONG } from "@/lib/constants"; | ||
import { | ||
drupalClientPreviewer, | ||
drupalClientViewer, | ||
} from "@/lib/drupal/drupal-client"; | ||
import { GET_ENTITY_AT_DRUPAL_PATH } from "@/lib/graphql/queries"; | ||
|
||
import { env } from "@/env"; | ||
import { fetchNodeByPathQuery } from "./get-node-nocache"; | ||
|
||
/** | ||
* Function to directly fetch a node from Drupal by its path and locale. | ||
* | ||
* @param path The path of the node. | ||
* @param locale The locale of the node. | ||
* @param revision The revision of the node. | ||
* @param isDraftMode If true, fetches the draft version of the node. | ||
* @returns The fetched node data or null if not found. | ||
*/ | ||
export async function fetchNodeByPathQuery( | ||
path: string, | ||
locale: string, | ||
isDraftMode: boolean, | ||
revision: string = null, | ||
) { | ||
const drupalClient = isDraftMode ? drupalClientPreviewer : drupalClientViewer; | ||
return await drupalClient.doGraphQlRequest(GET_ENTITY_AT_DRUPAL_PATH, { | ||
path, | ||
langcode: locale, | ||
revision, | ||
}); | ||
} | ||
Comment on lines
-14
to
-35
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I had to move this function to its own file, because we could not import an export from this file in middleware (the neshka/cache package causes webpack errors there) |
||
import { env } from "@/env"; | ||
|
||
// Here we wrap the function in react cache and nesh cache avoiding unnecessary requests. | ||
const cachedFetchNodeByPathQuery = neshCache(cache(fetchNodeByPathQuery)); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,9 @@ | ||
import { NextRequest, NextResponse } from "next/server"; | ||
import createMiddleware from "next-intl/middleware"; | ||
|
||
import { fetchNodeByPathQuery } from "@/lib/drupal/get-node-nocache"; | ||
import { extractRedirectFromRouteQueryResult } from "@/lib/graphql/utils"; | ||
|
||
import { routing } from "./i18n/routing"; | ||
import { auth, DEFAULT_LOGIN_REDIRECT_URL, DEFAULT_LOGIN_URL } from "./auth"; | ||
|
||
|
@@ -25,12 +28,85 @@ interface AppRouteHandlerFnContext { | |
params?: Record<string, string | string[]>; | ||
} | ||
|
||
// Handle redirects from Drupal | ||
async function handleDrupalRedirects(request: NextRequest) { | ||
const pathname = request.nextUrl.pathname; | ||
console.log("Handling Drupal redirects for path:", pathname); | ||
|
||
// We need to check if the path includes a locale. | ||
// Get the default locale from the routing configuration | ||
const defaultLocale = routing.defaultLocale; | ||
const pathnameSegments = pathname.split("/").filter(Boolean); | ||
|
||
// Handle locale determination | ||
let locale; | ||
let slugSegments; | ||
|
||
if (pathnameSegments.length === 0) { | ||
// Root path - use default locale | ||
locale = defaultLocale; | ||
slugSegments = []; | ||
} else { | ||
// Check if first segment is a valid locale | ||
const firstSegment = pathnameSegments[0]; | ||
const isLocale = routing.locales.includes( | ||
firstSegment as (typeof routing.locales)[number], | ||
); | ||
|
||
if (isLocale) { | ||
// Path includes locale: /en/about | ||
locale = firstSegment; | ||
slugSegments = pathnameSegments.slice(1); | ||
} else { | ||
// Path doesn't include locale: /about | ||
// Assume default locale | ||
locale = defaultLocale; | ||
slugSegments = pathnameSegments; | ||
} | ||
} | ||
|
||
// If there are no slug segments, it's likely the homepage | ||
if (slugSegments.length === 0) { | ||
return null; | ||
} | ||
|
||
// Construct path for query | ||
const path = "/" + slugSegments.join("/"); | ||
console.log("Constructed path for query:", path); | ||
console.log("Locale for query:", locale); | ||
|
||
try { | ||
// Check if this path should redirect | ||
const nodeByPathResult = await fetchNodeByPathQuery(path, locale, false); | ||
const redirectResult = | ||
extractRedirectFromRouteQueryResult(nodeByPathResult); | ||
|
||
if (redirectResult) { | ||
// Apply the appropriate redirect | ||
const status = | ||
redirectResult.status === 307 || redirectResult.status === 302 | ||
? redirectResult.status | ||
: 308; // Permanent redirect | ||
|
||
return NextResponse.redirect( | ||
new URL(redirectResult.url, request.url), | ||
status, | ||
); | ||
} | ||
} catch (error) { | ||
// If there's an error, continue to the page component | ||
console.error("Middleware error:", error); | ||
} | ||
|
||
return null; | ||
} | ||
|
||
Comment on lines
+31
to
+103
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here we handle the logic to understand if the path has the language in it also we ignore the frontpage |
||
const intlMiddleware = createMiddleware(routing, { | ||
alternateLinks: false, | ||
}); | ||
|
||
const authMiddleware = (request: NextRequest, ctx: AppRouteHandlerFnContext) => | ||
auth((req) => { | ||
auth(async (req) => { | ||
const isLoggedIn = req.auth?.user; | ||
const isProtectedRoute = PROTECTED_ROUTES.some((route) => | ||
req.nextUrl.pathname.startsWith(route), | ||
|
@@ -60,6 +136,12 @@ const authMiddleware = (request: NextRequest, ctx: AppRouteHandlerFnContext) => | |
} | ||
} | ||
|
||
// Check if there are redirects from Drupal for this request: | ||
const redirectResponse = await handleDrupalRedirects(request); | ||
if (redirectResponse) { | ||
Comment on lines
+139
to
+141
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added this here, after the checks for auth pages, and before the middleware for next-intl (not sure this is great) |
||
return redirectResponse; | ||
} | ||
|
||
return intlMiddleware(request); | ||
})(request, ctx); | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Basically here the idea is that once we get here we dont' need to check if there's a redirect, because that is handled by the middleware.