From a3c92cb86388adb9f5b725afc9bbd114b99d9a8b Mon Sep 17 00:00:00 2001 From: Aleksandra Date: Thu, 11 Aug 2022 13:20:57 +0200 Subject: [PATCH] Allow using RouteUrlObject as redirect.destination in getStaticProps and getServerSideProps (#3699) --- .changeset/clean-hats-pump.md | 5 ++ apps/web/package.json | 2 +- apps/web/pages/[postId]/index.tsx | 7 +++ apps/web/pages/page-with-gssp-redirect.tsx | 27 ++++++++ package.json | 2 +- packages/blitz-next/src/index-server.ts | 71 +++++++++++++++++++--- packages/blitz/src/types.ts | 4 ++ 7 files changed, 107 insertions(+), 11 deletions(-) create mode 100644 .changeset/clean-hats-pump.md create mode 100644 apps/web/pages/[postId]/index.tsx create mode 100644 apps/web/pages/page-with-gssp-redirect.tsx diff --git a/.changeset/clean-hats-pump.md b/.changeset/clean-hats-pump.md new file mode 100644 index 0000000000..76e685f842 --- /dev/null +++ b/.changeset/clean-hats-pump.md @@ -0,0 +1,5 @@ +--- +"@blitzjs/next": patch +--- + +Allow using `RouteUrlObject` as `redirect.destination` in `getStaticProps` and `getServerSideProps` diff --git a/apps/web/package.json b/apps/web/package.json index f2e18b1656..d34b1eedbb 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -4,7 +4,7 @@ "private": true, "scripts": { "start:dev": "pnpm run prisma:start && next dev", - "buildapp": "prisma generate && next build", + "buildapp": "NODE_ENV=production pnpm blitz codegen && pnpm prisma generate && next build", "start": "next start", "lint": "next lint", "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next", diff --git a/apps/web/pages/[postId]/index.tsx b/apps/web/pages/[postId]/index.tsx new file mode 100644 index 0000000000..9691e4c98f --- /dev/null +++ b/apps/web/pages/[postId]/index.tsx @@ -0,0 +1,7 @@ +export default function Post() { + return ( +
+

Post

+
+ ) +} diff --git a/apps/web/pages/page-with-gssp-redirect.tsx b/apps/web/pages/page-with-gssp-redirect.tsx new file mode 100644 index 0000000000..e315b13890 --- /dev/null +++ b/apps/web/pages/page-with-gssp-redirect.tsx @@ -0,0 +1,27 @@ +import {SessionContext} from "@blitzjs/auth" +import {Routes} from "@blitzjs/next" +import {gSSP} from "app/blitz-server" + +type Props = { + userId: unknown + publicData: SessionContext["$publicData"] +} + +export const getServerSideProps = gSSP(async ({ctx}) => { + const {session} = ctx + + return { + redirect: {destination: Routes.Post({postId: "1"})}, + props: { + userId: session.userId, + publicData: session.$publicData, + publishedAt: new Date(0), + }, + } +}) + +function PageWithGsspRedirect(props: Props) { + return
{JSON.stringify(props, null, 2)}
+} + +export default PageWithGsspRedirect diff --git a/package.json b/package.json index fb3e666439..89bea33d87 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "preinstall": "npx only-allow pnpm", "prepare": "husky install", "build": "turbo run build", - "build:apps": "turbo run buildapp", + "build:apps": "turbo run buildapp --concurrency=1", "dev": "turbo run dev --no-cache --parallel", "lint": "turbo run lint", "test": "turbo run test", diff --git a/packages/blitz-next/src/index-server.ts b/packages/blitz-next/src/index-server.ts index c84de79ec6..b37dbdfc09 100644 --- a/packages/blitz-next/src/index-server.ts +++ b/packages/blitz-next/src/index-server.ts @@ -1,5 +1,5 @@ -import type {NextConfig} from "next" -import { +import type { + NextConfig, GetServerSideProps, GetServerSidePropsResult, GetStaticProps, @@ -30,6 +30,8 @@ import {IncomingMessage, ServerResponse} from "http" import {withSuperJsonProps} from "./superjson" import {ParsedUrlQuery} from "querystring" import {PreviewData} from "next/types" +import {resolveHref} from "next/dist/shared/lib/router/router" +import {RouteUrlObject, isRouteUrlObject} from "blitz" export * from "./index-browser" @@ -50,6 +52,25 @@ type SetupBlitzOptions = { onError?: (err: Error) => void } +export type Redirect = + | { + statusCode: 301 | 302 | 303 | 307 | 308 + destination: string | RouteUrlObject + basePath?: false + } + | { + permanent: boolean + destination: string | RouteUrlObject + basePath?: false + } + +export type BlitzGSSPResult

= {props: P | Promise

} | {redirect: Redirect} | {notFound: true} + +export type BlitzGSPResult

= + | {props: P; revalidate?: number | boolean} + | {redirect: Redirect; revalidate?: number | boolean} + | {notFound: true; revalidate?: number | boolean} + export type BlitzGSSPHandler< TProps, Query extends ParsedUrlQuery = ParsedUrlQuery, @@ -59,8 +80,8 @@ export type BlitzGSSPHandler< req, res, ...args -}: Parameters>[0] & {ctx: Ctx}) => ReturnType< - GetServerSideProps +}: Parameters>[0] & {ctx: Ctx}) => Promise< + BlitzGSSPResult > export type BlitzGSPHandler< @@ -70,9 +91,9 @@ export type BlitzGSPHandler< > = ({ ctx, ...args -}: Parameters>[0] & {ctx: Ctx}) => ReturnType< - GetStaticProps -> +}: Parameters>[0] & {ctx: Ctx}) => + | Promise> + | BlitzGSPResult export type BlitzAPIHandler = ( req: NextApiRequest, @@ -123,7 +144,12 @@ export const setupBlitzServer = ({plugins, onError}: SetupBlitzOptions) => { try { const result = await handler({req, res, ctx, ...rest}) - return withSuperJsonProps(withDehydratedState(result, getClient())) + return withSuperJsonProps( + withDehydratedState( + normalizeRedirectValues>(result), + getClient(), + ), + ) } catch (err: any) { onError?.(err) throw err @@ -143,7 +169,12 @@ export const setupBlitzServer = ({plugins, onError}: SetupBlitzOptions) => { try { const result = await handler({...context, ctx: ctx}) - return withSuperJsonProps(withDehydratedState(result, getClient())) + return withSuperJsonProps( + withDehydratedState( + normalizeRedirectValues>(result), + getClient(), + ), + ) } catch (err: any) { onError?.(err) throw err @@ -219,6 +250,7 @@ export type PrefetchQueryFn = >( options?: DefaultOptions, ) => Promise +type BlitzResult = Partial & BlitzGSSPResult> type Result = Partial & GetStaticPropsResult> function withDehydratedState(result: T, queryClient: QueryClient | null) { @@ -229,6 +261,27 @@ function withDehydratedState(result: T, queryClient: QueryClie return {...result, props: {...("props" in result ? result.props : undefined), dehydratedState}} } +// Converts Blitz's GetServerSidePropsResult and GetStaticPropsResult to a NextJS compatible format +// Blitz accepts string | RouteUrlObject as redirect.destination — this function converts it to a string +const normalizeRedirectValues = ( + result: BlitzResult, +): NormalizedResult => { + if ("redirect" in result) { + const dest = result.redirect?.destination + if (dest && isRouteUrlObject(dest)) { + // Todo: find a better way to resolve href without `as any` assertion. + const resolvedDest = resolveHref({asPath: "/", pathname: "/"} as any, dest, true) + + return { + ...result, + redirect: {...result.redirect, destination: resolvedDest[1] || resolvedDest[0]}, + } as NormalizedResult + } + } + + return result as NormalizedResult +} + declare module "blitz" { export interface Ctx { prefetchQuery: PrefetchQueryFn diff --git a/packages/blitz/src/types.ts b/packages/blitz/src/types.ts index e63efcf519..5ab173491a 100644 --- a/packages/blitz/src/types.ts +++ b/packages/blitz/src/types.ts @@ -6,6 +6,10 @@ export interface RouteUrlObject extends Pick { pathname: string } +export const isRouteUrlObject = (x: any): x is RouteUrlObject => { + return typeof x === "object" && "pathname" in x && typeof x.pathname === "string" +} + export type AsyncFunc = (...args: any) => Promise /**