wpengine / faustjs

Faust.js™ - The Headless WordPress Framework
https://faustjs.org
Other
1.43k stars 131 forks source link

RFC: Next.js App Router support in Faust #1520

Closed blakewilson closed 8 months ago

blakewilson commented 1 year ago

We have recently done some research into seeing how we can support the Next.js App Router in Faust. As we begin to build out experimental support for App Router this quarter, we would very much appreciate the community's feedback and involvement in this milestone!

Problem Statement

Next.js recently introduced a new feature called "App Router" in which a new directory, called "app", is used to create and route pages. These pages work differently than the file based pages we see in the "pages" directory. Instead of a single file containing the React presentation component and the SSR/SSG counterpart, like getServerSideProps or getStaticProps, the App Router makes use of several files to create one presentation. Since Faust uses these SSR/SSG counterparts to fetch data, authenticate, etc. this poses an issue for supporting the App Router with the current implementations in Faust. Additionally, with Next.js shifting to React Server Components, we will need to come up with solutions for fetching data, authenticating, etc, all on the server within RSCs (React Server Components).

Solution

We create developer friendly utilities for handling Data Fetching, Authentication, Previews, etc. that work within RSCs. There are a few benefits to going this route:

1.  Since we are supporting RSCs as a whole, we can become more framework agnostic in our approach and possibly support a wider array of React implementations 2.  We lessen the reliance on our "all or nothing" approach we currently see in Faust. These utilities become more of a "library of tools" vs a "framework" that you have to completely opt into.

These utilities would start as experimental either in our current NPM packages or as a new NPM package.

The first experimental utilities we will ship will be:

How the App Router Works

Pages

Next.js describes this as "UI that is unique to a route". These page.tsx files can be added to appropriate directories to create URL structures. For example: app/about/page.tsx will resolve to localhost:3000/about.

Pages also use React Server Components by default. This means that you can run async operations, such as fetch within your components, and that state will be available immediately on page load. The caveat here is there are no longer SSR/SSG utilities like getServerSideProps or getStaticProps (an area where Faust does a lot of the heavy lifting).

https://nextjs.org/docs/app/building-your-application/routing/pages-and-layouts#pages

Layouts

Next.js describes this as "UI that is shared between multiple pages". You can imagine this is a shared header or footer. Layouts can be nested as well. So you can have a shell layout for elements that exist on every page, but also have a separate layout component for all content sections as well.

https://nextjs.org/docs/app/building-your-application/routing/pages-and-layouts#layouts

Errors

There is a new Next.js concept call error.js pages which can handle error boundaries in your respective components. This uses React Suspense under the hood.

https://nextjs.org/docs/app/building-your-application/routing/error-handling

Considerations

Plugin System Filters

Our current plugin system ties in everywhere in Faust, with context objects that give context to each filter. We would want to determine if we use completely new filters for these new helper utilities, or use the same filters where applicable but add flags for isAppRouter or isPagesRouter. My gut says we would want entirely separate filters, but the community's input here would be appreciated.

Server Side Authentication

Currently in Faust, all authentication happens client side. The refresh token is stored in a cookie, our client side helper gets that token and makes a request for an access token. Then with that access token, we make a request for authenticated data.

We will need to replicate this ability on the server side as this is vital to RSCs.

Depending on the implementation, this could either be a separate auth implementation specifically for App Router, or we could update the current auth in Faust to support SSR authentication.

Toolbar Support

Toolbar, or at least the way it is implemented may look a bit different in an App Router context. Since we don't have a global component (<WordPressNode />) to handle our routing, the user would have to implement the component themselves. I, however, don't see this as a bad thing. I see this as another indicator of us leaning into the "library" approach vs the "framework" approach. Toolbar is just another utility that can be used as the user see's fit.

In addition, the Toolbar can be used as an RSC, giving a better developer experience as there is no client side loading/viewport change that we currently see in Faust.

SEO

With the introduction of App Router, there is a new file called head.js that has custom support for fetching metadata. As this is currently a pain in Faust, there is an opportunity here to create another utility function to get SEO data from WordPress automatically, without the user having to configure anything.

New NPM Package

We may want to consider shipping these new features in a new NPM package. This would depend on if we can easily isolate these features into a new package or not. But seeing Apollo do this may be indicative that we may want to follow suit.

Caveats

Template Hierarchy

With the introduction of the App Router, the mental model of what a Next.js page is composed of shifted. In the traditional file based page sense, a Next.js page logically is quite similar to a WordPress template. Except one is routed/determined by content (WordPress) and one is determined by the URL path (file based page routes in Next.js). However, with the App Router, this changes drastically. It is no longer one page that comprises a UI in Next.js, it could be several. There could be several layouts, several error boundaries, etc. The mental model of a single file template no longer fits into the mental model of the App Router. With this in mind, I think it's best we don't bring conventional support for this feature to App Router and instead create a Faust utility that can be used to get the possible templates from a given Next.js App route.

Supporting the Pages Router

The app router is a large change, It is a total paradigm shift in Next.js. We will have to decide on how we support these two routers going forward. Do we have production support for both? Do we slowly phase out support for the pages directory? Etc.

Resources

blakewilson commented 1 year ago

A small POC was created in regards to these findings:

https://github.com/blakewilson/faust-app-dir-poc

The readme contains instructions to install it and configure yourself.

blakewilson commented 1 year ago

@theodesp Posted his thoughts in the POC repo. I'm copying them here as well for visibility:

Hey @blakewilson. Great work here. Thats a good starting point for leveraging Next.js App router.

In regards to my feedback, I think that in general terms we need a further push to provide most of the functionality of the original Faust.js. Here are some of my thoughts:

  • Plugins: We should definitely support the new plugin hooks that should work both in the server-side and in the client side and maybe have a mechanism that shares data between them.
  • Provider: We should include aFaustProvider that is "client only" but accept server components as children. This provider should deal with setting up a client content for the rest of the application. Ideally we should ask the developer to place this provider on the Root Layout component or if they wish to, on a child layout component.
  • API handler: We should still provide an API handler that deals with cross-cutting concerns like authentication, logging, API fetch requests end so on.
  • Next-Auth Support: I think this is more important that ever since justlevine Feature Request here: https://github.com/wpengine/faustjs/issues/1512 Ideally we should provide our own Auth provider that satisfies the Next-Auth interface here: https://next-auth.js.org/providers/credentials.
  • client side hooks: We should developer client side hooks that deal with common queries like auth or fetching.
  • wp-templates: We should consider supporting this in terms of having a catch-all segment that renders aWordPressTemplate component and perform the seedQuery request as usual. However this time we may have to make sure that this will render everything server side and allow the user to customize this catch-all segment to render specific templates. Ideally this would a convention and for cases where users want to continue using the wp-template hierarchy system in the app folder.
  • file-conventions: We should consider providing our own conventions for using the various next.js
  • Post Previews: We should consider using the next.js draft mode and have a mechanism to trigger and disable this when we try to preview a post/ page in WordPress. Maybe we can consider a custom hook or a timeout and then using the FaustProvider expose a boolean flag into the system that informs the developer that they are currently previewing content.
  • Bundle Size: We should make use of the Apollo Client optional in the client side and make sure the bundle size stays small.
justlevine commented 1 year ago

Love this!

(If this gathers the amount of community feedback I'm hoping it will, I suggest adopting a .md approach, so discussion on particular aspects can be grouped/ more easily managed).

My feedback (only commenting when I have something to add, if it's not mentioned assume I agree - especially with @theodesp 's feedback ):

Server Side Authentication

Depending on the implementation, this could either be a separate auth implementation specifically for App Router, or we could update the current auth in Faust to support SSR authentication.

In general, I think anything that can be shared across RSC and client-side is better for migration and DX. That said, there's a lot of baggage around the current auth (#1512), so if a separate implementation is necessary to make it more modular and extendable I think that should win out.

@theodesp

Next-Auth Support: I think this is more important that ever since justlevine Feature Request here: https://github.com/wpengine/faustjs/issues/1512

IMO this is putting the cart before the horse (and is kind of the opposite of the intent of that feature request). The majority of WP sites should not be using NextAuth, so we should prioritize creating a good server-side auth DX before muddying around with opinionated implementations for syncing client-side/server-side user identities.

SEO

Please, please, please, make this extensible and agnostic by default. There are 3 different SEO plugins that currently support WPGraphQL, and I've come across a dozen custom implementations along the way.

Template Hierarchy

This should be a foundational opportunity, not a caveat. The App Router doesn't just allow for WP endpoints that currently require hacky workarounds, but unblocks classic and FSE templates and template parts (both theme-bundled and Editor-created).

With this in mind, I think it's best we don't bring conventional support for this feature to App Router and instead create a Faust utility that can be used to get the possible templates from a given Next.js App route.

I'm not entirely sure what you mean by conventional support vs a utility, but it would be nice to get all the templates out of the shared js bundle. Regardless of pattern, the end result should be additive: a catch-all that handles the boilerplate WP routing, with manual app/ routes letting uses to overload/extend the defaults for specific use cases.

file-conventions

@theodesp

We should consider providing our own conventions for using the various next.js

As a long-time user of FaustJS, this makes me really nervous. From experience, FaustJS shines when its open, agnostic, and meets dev on their level, whereas the opinionated patterns (e.g. gqty, the v1 hook system, current auth implementation) end up creating more friction and then cost more time for both the Faust team and users once they (eventually/hopefully) get reworked.

brandensilva commented 1 year ago

I'm very much aligned with the less meta framework and more utility-based focus nature here being framework agnostic (Next, Vue, Svelte, Solid). This seems more beneficial to the open source ethos for allowing more headless approaches to seamlessly connect to WP and subsequently WP Engine.

While we have gone heavily on the Next.js App Router in recent projects, given its opinionated nature, it may not be in everyones cup of tea so I see the merit of less conventions with more dev flexibility.

As far as WordPress conventions around templating and routing and bringing that into a React framework, we are a mixed bag on how much Faust needs to do for us.

For routing, we are fine with Next's routing system and re-creating that ourselves with whatever conventions we need given routing can differ quite a bit from framework to framework but we obviously really like the idea of maintaining previews out of client familiarity and linking being consistent between WordPress and Next.

For templating the utilities approach that Faust App Router POC could bring is probably more than enough for our needs to bootstrap a project. I don't think it should be a necessity however, but obviously we want to maintain client choice of template swapping.

So in our minds in how we approach projects it is more beneficial the Wordpress side adapts to the headless approach more than the other way around. If we need things (adding toolbars, generating templates, etc) it seems like these should all be optional in the Headless frameworks you guys choose to support. Perhaps a CLI generator that allows you to pick and choose how much Faust does that gives a lot of flexibility for devs to pick the parts they need for their headless solutions.

Where Faust shines in conjunction with WPGraphQL that doesn't really exist elsewhere though without a lot of pain or hacks is the block generation, block rendering, and updates without long builds. Leaning on creating more reusable blocks components and utilities that align with these features save us and our clients a lot of time, but obviously there is more to figure out given the 1:1 data sync between Faust WP and Faust in an App Router server/client side world in general. This is just where we see huge value add from Faust so we are excited to see what you guys come up with!

blakewilson commented 1 year ago

Hey @justlevine and @brandensilva,

Thank yall very much for your feedback! We've taken these thoughts and created the initial version of our App Router package (@faustwp/experimental-app-router) and an example project:

https://github.com/wpengine/faustjs/tree/canary/examples/next/app-router

We also have a guide detailing how to get started with this new project and our App Router utilities:

https://faustjs.org/tutorial/getting-started-with-the-experimental-app-router

blakewilson commented 1 year ago

Also, @Fran-A-Dev and I will be hosting an event on Oct 25 at 1pm CST for diving into this experimental App Router support and Next.js 13. You can register at the link here:

https://wpeng.in/approuter/

Hope to see yall there!

justlevine commented 1 year ago

Thanks for the update, @blakewilson . Now that it's in canary, where would the best place to leave feedback, here or in a separate issue(s)?

brandensilva commented 1 year ago

Perfect thanks @blakewilson. I've already I implemented the new experimental app router on a project actually but I'll sign up all the same since I have some questions.

Thanks for your continued work on this.

blakewilson commented 1 year ago

@justlevine That's a great question. For any new issues relating to our App Router support, please open a new issue and tag this RFC for reference. We'll be closing this RFC.

blakewilson commented 1 year ago

Awesome @brandensilva! Care to share the project? 😄 See ya there.

justlevine commented 1 year ago

@blakewilson how does the App Router work with wp-templates? I'm not seeing any obvious reference to this in the example or docs.

blakewilson commented 8 months ago

@blakewilson how does the App Router work with wp-templates? I'm not seeing any obvious reference to this in the example or docs.

@justlevine Currently the experimental-app-router package does not have support for Faust templates. I've added an issue for us to research the possibilities here: https://github.com/wpengine/faustjs/issues/1760

blakewilson commented 8 months ago

Since we have released the experimental-app-router package, I'll be closing this RFC. If you have any future feature requests or bug issues, please open them as a separate issue. Thanks y'all for your feedback and helping shape Faust App Router support!