Guidance on creating a catchall page with getServerSideProps instead of getStaticProps

What are you trying to do? (please be as specific as possible and include relevant screenshots, code snippets, and reproduction steps)

I’m working on an open source component library for Plasmic that makes use of Supabase auth without Plasmic auth (see plasmic-supabase). It’s designed to be used with Plasmic apps published to nextjs (pages router) with loader API.

Authentication via Supabase is working fine, but I’m having trouble making a reliable method to create login protected pages (ie pages that will redirect to the /login page if the user does not have an active session with Supabase auth).

The details of how to check if a user is authenticated are not important to my question. Let’s just assume that we DO have a reliable method of checking that a user is authenticated. The focus of this question is how to configure nextjs to work well with Plasmic to create login protected pages.

What have you tried so far? (please link relevant docs and other forum posts)

  1. Create new plasmic project in Plasmic studio
  2. Add a blank page “/login” and “/private” via Plasmic studio. Add link elements to the homepage, login and private pages to all other pages (eg homepage has links to login and private)
  3. Publish to github using these settings
    image
  4. Open the published repo on my local machine
  5. Run npm install to install dependencies
  6. Start my dev server npm run dev so the app is running at localhost:3000
  7. Configure custom app host in Plasmic studio to point to my local dev server

Now I adjust a few things as per this example repo branch

  1. Create two pages that are publicly accessible by adding specific routes index.tsx and login.tsx
  2. Modify the standard [[...catchall]].tsx page and rename it to [...catchall].tsx and then replace it’s contents so it loads with getServerSideProps instead (see […catchall].tsx)

This works well, however I noticed a few issues

  1. Navigating to/between login protected pages is quite slow because PLASMIC.maybeFetchComponentData and extractPlasmicQueryData runs EVERY time the page loads without the benefits of caching that would normally occur when these run in the standard catchall page with getStaticProps
  2. This very occasionally takes more than 5 seconds and causes a published nextjs app on vercel to timeout when loading the page

So then I tried a different […catchall].tsx page as shown in this OTHER example repo branch[…catchall].tsx

The new method still runs the auth check in getServerSideProps but fetches plasmic component data (maybeFetchComponentData) client-side so that login protected pages can load faster, and the result of maybeFetchComponentData can be cached.

This still works (if a user is not authenticated and they access a private page, they are redirected to login page). Page load speed is improved, and there’s far less calls to plasmic to get component data.

however I get an error message in console

How that error message arises is demoed in this short video

My questions are

  1. Is the error message about code export hashes a problem?
  2. Is there a better way to configure the catchall page if needing to use getServerSideProps() instead of getStaticProps()
  3. Is there any better way to make pages login protected & make it play nicely with Plasmic, without needing Plasmic auth?
1 Like

Hi

  1. No, we should change this message, it can be an issue when you have nested instances of <PlasmicRootProvider>. But we are showing too much right now.
  2. Plasmic data is cached through CDN. However, it does need to make some trips to the server to check if a new version of your project is available. If you need higher performance, you can add a caching layer to your application.
  3. That’s a great question. A simple approach would be adding a wrapper to your page to perform the check. But since you are doing with the getServerSideProps, the ideal approach would be to check there directly. We allow you to include additional data in the page metadata in free form. You could include this metadata in your pages. Then, you can fetch plasmic data for the page and match this metadata with your protection mechanism. Overall, it is a “hack” solution, but it should allow you to achieve your desired result.
const plasmicData = await PLASMIC.maybeFetchComponentData(...);
const generalMetadata = plasmicData.entryCompMetas[0].metadata;
if (generalMetadata?.isProtected === "...") {
   ...
}

Feel free to ask more about it. We planned to create such an example of integration so we can help you achieve it.

1 Like

Thanks so much @fmota

  1. OK thanks
  2. Thanks - any pointers on how to do this? I know that getStaticProps() caches already, I didn’t think any caching was available for getServerSideProps()?
  3. Thanks - that makes sense and I did play with that. If I can get (2) working better then that would be a great solution to allow specifying whether a page is protected or public from plasmic studio

Do you have a more detailed description of the timings for maybeFetchComponentData, which is seen solely for this function? Please also specify which region you are deploying your application to. We may be able to improve it a bit on our end.

For extractPlasmicQueryData, it may be the case that the additional overhead comes from the need to do an additional render of the application to collect the endpoints to be fetched, if you manage the data queries on your own and not need to extract the data with our function helper. You can use a global context to provide the data you fetch on your own instead Global Contexts | Learn Plasmic. Could you also provide a breakdown of how much time is going into this? 5 seconds is too much, so maybe there is some detail missing.

Hi @fmota
Thanks for your reply.

After looking into this a lot more, I actually don’t think extractPlasmicQueryData is causing slowness.

The ./pages/[...catchall].tsx page (code below) fails frequently with a 10 second timeout at the await supabase.auth.getUser() line, well before we do anything with fetching from Plasmic.

No API call ever reaches Supabase when the timeout occurs, and if I insert a try catch block within getServerSideProps no errors occur. It seems to simply timeout at Vercel’s standard 10 second limit pretty often.

Timeout occurs more frequently when navigating or refreshing a page after a period of inactivity. The issue persists even when paying for the first level Vercel plan, rather than relying on the more limited free plan.

I’m wondering if this is occuring because of the large page size of the catchall page? (I frequently get warnings in terminal console when running my local dev server about large page size impacting performance).

An example of a page that times out frequently is this one: Plasmic

This issue is worth noting if you’re wanting to publish example code that uses getServerSideProps() & I’m still really curious about what’s going on. So if you have any further insights, I’d love to hear them.

However, for my project, I’ve transitioned back to the standard [[…catchall]].tsx page generated when you publish Plasmic projects to nextjs with the loader API. And I’m login protecting pages with nextjs middleware instead. This has avoided the issue described above and is a cleaner, simpler, solution for my needs anyway. The plasmic-supabase repo will be updated accordingly soon to give new instructions on using middleware for login-protecting pages.

Here is the ./pages/[...catchall].tsx code showing the actual API call to supabase, which is where it times out without any API call sent to Supabase

// ./pages/[...catchall].tsx

/* 
  Catchall page that runs for every page EXCEPT /, /login, and /public/*
  These pages are login protected by default
  The logic for checking authorization & where to redirect if a user is not authorized is controlled
  by @/authorization-settings.ts.
  The authorization-settings.ts file should export:
  - authorizationCheckFunction: a function that returns true if the user is authorized to view the page
  - loginPagePath: where to redirect to if authorization fails  eg '/login'
  
  The routes that render through this page are rendered on-demand (getServerSideProps instead of getStaticProps) 
  because they are login protected. This ensures that the user's session is checked on every request
  and avoids login-protected pages being cached and related issues.

  This pages is a modified various of the standard Plasmic NextJS loader API catchall page.

  Pages created in Plasmic studio will render using this catchall if it's:
    Page Settings -> URL path does NOT start with '/public/' and is not "/" or "/login"
*/

import type { GetServerSideProps } from "next";
import { createClient } from 'plasmic-supabase/dist/utils/supabase/server-props'

import * as React from "react";
import {
  PlasmicComponent,
  PlasmicRootProvider,
} from "@plasmicapp/loader-nextjs";

import Error from "next/error";
import { useRouter } from "next/router";
import { PLASMIC } from "@/plasmic-init";
import useSWR from "swr";

import { authorizationCheckFunction, loginPagePath } from "@/authorization-settings";

export default function PlasmicLoaderPage(props: {
  plasmicPath: string;
}) {

  const router = useRouter();

  //Fetch the component (page) data from Plasmic and cache it with SWR
  //Note that when navigating between [[...catchall]].tsx and this page
  //A warning  from Plasmic will appear in console https://github.com/plasmicapp/plasmic/blob/7117b4c2de9e89f4435db9efa0cba6a00012c297/packages/loader-react/src/loader-shared.ts#L498
  //Because maybeFetchComponentData will fetch designs with query string parameter browserOnly=true here
  //But browserOnly=false from [[...catchall]].tsx
  //Because fetching of Plasmic componet data is happening client side here, but server side in [[...catchall]].tsx
  //This does not appear to matter since the referenced file above seems to gracefully handle this case
  //However if the error could be removed by some refactor, it would be ideal

  const plasmicComponentFetcher = React.useCallback(async () => {
    return await PLASMIC.maybeFetchComponentData(props.plasmicPath);
  }, [props.plasmicPath]);

  const { data: plasmicData, error, isValidating } = useSWR(
    `plasmicData_${props.plasmicPath}`,
    plasmicComponentFetcher
  );

  //Render the error page if there is an error
  if(error) {
    return <Error statusCode={500} />;
  }

  //Render a loading message if the data is still loading
  if(isValidating && !plasmicData) {
    return <div>Loading...</div>;
  }

  //Render a 404 page if the page is not found in Plasmic
  if ((!isValidating && (!plasmicData || plasmicData.entryCompMetas.length === 0))) {
    return <Error statusCode={404} />;
  }

  //Extract the page meta data from the Plasmic data
  const pageMeta = plasmicData!.entryCompMetas[0]

  //Render the Plasmic component (page)
  return (
    <PlasmicRootProvider
      loader={PLASMIC}
      prefetchedData={plasmicData!}
      prefetchedQueryData={{}}
      pageParams={pageMeta.params}
      pageQuery={router.query}
    >
      <PlasmicComponent component={pageMeta.displayName} />
    </PlasmicRootProvider>
  );
}

//This runs on the server while rendering
//Unlike the pages in the root directory, we run this every time the page is requested with no cache
//This is appropriate because these pages are login protected and only work with a valid session
//We also need to recheck each time the page is requested to ensure the user is still authenticated
export const getServerSideProps: GetServerSideProps = async (context) => {

  //Get the catchall parameter from the page context
  const { catchall } = context.params ?? {};

  //Get the path of the current page
  let plasmicPath = typeof catchall === 'string' ? catchall : Array.isArray(catchall) ? `/${catchall.join('/')}` : '/';

  //Determine if the user is authorized to view this page
  const supabase = createClient(context);

  const { data: { user } } = await supabase.auth.getUser();

  const isAuthorized = authorizationCheckFunction(plasmicPath, user);

  if(isAuthorized !== true) return {
    redirect: {
      destination: loginPagePath,
      permanent: false,
    }
  }

  //We don't try and fetch the plasmic component data or data on the page here, because getServerSideProps does not cache
  //Therefore we would run this every time without cache, causing slow page navigation
  //Instead, we do that client-side and cache results with SWR (see above)

  return { props: { plasmicPath } };
}

Could you increase the timeout to check it Configuring Maximum Duration for Vercel Functions? It shouldn’t be the case, I see the server answering in ~200ms when the request is not cached in the CDN, so it’s hard for me to see it consuming 10 seconds.