diff --git a/next/src/app/[locale]/(dynamic)/[...slug]/page.tsx b/next/src/app/[locale]/(dynamic)/[...slug]/page.tsx index 46e4e9c3..1e1f89df 100644 --- a/next/src/app/[locale]/(dynamic)/[...slug]/page.tsx +++ b/next/src/app/[locale]/(dynamic)/[...slug]/page.tsx @@ -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}`); - } - // 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: diff --git a/next/src/app/[locale]/(static)/nodepreview/page.tsx b/next/src/app/[locale]/(static)/nodepreview/page.tsx index 7f7fff09..a24ba64e 100644 --- a/next/src/app/[locale]/(static)/nodepreview/page.tsx +++ b/next/src/app/[locale]/(static)/nodepreview/page.tsx @@ -2,7 +2,7 @@ import { setRequestLocale } from "next-intl/server"; import { Node } from "@/components/node"; import NotFoundPage from "@/components/not-found-page"; -import { fetchNodeByPathQuery } from "@/lib/drupal/get-node"; +import { fetchNodeByPathQuery } from "@/lib/drupal/get-node-nocache"; import { extractEntityFromRouteQueryResult } from "@/lib/graphql/utils"; async function DrupalPreviewPage({ searchParams, params: { locale } }) { diff --git a/next/src/lib/drupal/get-node-nocache.ts b/next/src/lib/drupal/get-node-nocache.ts new file mode 100644 index 00000000..77e659b2 --- /dev/null +++ b/next/src/lib/drupal/get-node-nocache.ts @@ -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, + }); +} diff --git a/next/src/lib/drupal/get-node.ts b/next/src/lib/drupal/get-node.ts index acd53b40..cf70e2e7 100644 --- a/next/src/lib/drupal/get-node.ts +++ b/next/src/lib/drupal/get-node.ts @@ -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, - }); -} +import { env } from "@/env"; // Here we wrap the function in react cache and nesh cache avoiding unnecessary requests. const cachedFetchNodeByPathQuery = neshCache(cache(fetchNodeByPathQuery)); diff --git a/next/src/middleware.ts b/next/src/middleware.ts index 8770022a..a6d9ed14 100644 --- a/next/src/middleware.ts +++ b/next/src/middleware.ts @@ -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; } +// 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; +} + 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) { + return redirectResponse; + } + return intlMiddleware(request); })(request, ctx);