Loading video player...
[Music]
Okay. Hello everyone. My name is Aurora.
I'm a web developer from Norway. I work
as a consultant uh at Crane Consulting
also. And I'm actively building with an
XJS app router in my current consultancy
project. Today I'm going to be teaching
you patterns regarding composition,
caching, and architecture in modern XJS
that will help you ensure scalability
and performance.
Let me first first refresh the most
fundamental concepts for this talk.
static and dynamic rendering. We
encounter them both in the nextG app
router.
Static rendering allows us to build
faster websites because pre-rendered
content can be cached and globally
distributed, ensuring users can access
it quicker.
For example, the next comp website.
Static rendering reduces server load
because content does not have to be
generated for each user request.
Pre-render content is also easier for
search engine crawers to index as the
content is already available on page
load.
Dynamic rendering on the other hand
allows our application to display
real-time or frequently updated data. It
also enables us to serve personalized
content such as dashboards and user
profiles.
For example, the Versell dashboard. With
dynamic rendering, we can access
information that can only be known at
request time. In this case, which user
is accessing their dashboard, which is
me.
There are certain APIs that can cause a
page to dynamically render. Usage of the
params and search params props that are
passed to pages or their equivalent
hooks will cause dynamic rendering.
However, with params, we can predefine a
set of pre-rendered pages using generate
static params and we can also cache the
pages as they're being generated by
users.
Furthermore, reading incoming request
cookies and headers will opt the page
into dynamic rendering. Unlike with
params though trying to cache or
preender anything using headers or
cookies will throw errors during build
because that information cannot be known
ahead of time.
Lastly using fetch with a data cache
configuration no store will also force
dynamic rendering. So these are the few
uh there are a few more APIs that can
cause dynamic rendering but these are
the ones we most commonly encounter.
In previous versions of next uh a page
would be rendered either as fully static
or fully dynamic. one single dynamic API
on a page without the whole page into
dynamic rendering. For example, doing a
simple off check for the value of a
cookie.
By utilizing React server components
with suspense, we can stream in dynamic
content like a personalized welcome
banner or recommendations as they become
ready and provide only fallbacks with
suspense while showing static content
like a newsletter.
However, once we add in multiple async
components on a dynamic page like a
feature product, they too would run at
request time even though they didn't
depend on dynamic APIs.
So to avoid blocking the initial page
load, we would suspend and stream those
components uh down as well doing extra
work creating skeletons and worrying
about things like human layout shift.
However, pages are often a mix of static
and dynamic content. for example, an
e-commerce app dependent on user
information while still containing
mostly static data. Being forced to pick
being forced to pick between them um
between static or dynamic causes lots of
redundant processing on the server on
content that uh never or very rarely
changes and isn't optimal for
performance.
So to solve this problem at last year's
nextJS comp the user use cache directive
was announced and this year as we saw in
the keynote it's available in the next
in nextg 16. So with use cache pages
will no longer be forced into either
static or dynamic rendering. They can be
both and next.js no longer has to guess
what a page is based on whether it
accesses things like params. Everything
is dynamic by default and use cache let
us explicitly opt into caching.
Use cache enables composable caching. We
can mark either a page, a react
component or function as cachable.
Here we can actually cach the feature
products component because it does not
need the request and processing and does
not use dynamic APIs.
And these cache segments can further be
preended and included as a part of the
static shell with partial preendering
meaning the feature products is now
available on page load and does not need
to be streamed.
So now that we have this important
background knowledge, uh let's do a
demo. An improvement of a codebase with
common issues often encountered in
nextgs apps. These include deep drilling
making it hard to maintain and refactor
features, redundant client side
JavaScript and large components with
multiple responsibilities and lack of
static rendering leading to additional
server costs and degraded performance.
So yeah, let's begin.
And give me one sec over here.
Um,
all right. Great. So, this is very
simple application. It's like inspired
by an e-commerce platform. And let me do
like an initial demos here. So, I can
load this page. I have like some content
like this feature product. I have
feature categories, different um product
data. There's also this um browse all
page over here where I can see um all of
the products in the platform and page uh
page between them. Then we have this
about page over here which is just
static. I can also sign in as a user and
uh that will log me into my user and
also get then personalized content on my
dashboard here like for example uh
recommended products or um this um
personalized discounts here.
So notice here there's a pretty good
mix. Uh al also one more page that I
forgot to show you, the product page,
the most important one. Uh also here we
can see product information and then um
save it if we like for our user. So
notice that there's a pretty good mix
here of static and dynamic content in
this app because of all of our user
dependent features. Let's also have a
look at the code
which would be over here. So I'm using
the app router here. Of course, in nextg
16, I have all of my different pages
like the about page, the all page, uh
product page. I also have uh I'm using
feature slicing here to uh keep my app
folder clean. I have like different
components and and queries uh talking to
my database with Prisma. Um so yeah, and
I purposely slowed all of this down. So
that's why we have these really long
loading states just so we can easier see
what's happening.
So the common issues that we wanted to
work on here that we actually have in
this application was deeprop drilling u
making it hard to maintain and refactor
features um excess client side
javascript um and add um lack of static
rendering leading to additional server
cost and degraded performance. So the
goal here of the demo is basically just
to improve this app with um some smart
patterns regarding uh composition
caching and architecture to fix those
common features and make it faster and
more scalable and easier to maintain.
So let's begin with that. The first
issue we want to fix is actually related
to prop chilling and um that would be
over here in the peach.
Notice um right here I have this logged
in variable at the top here
and you can see I'm passing it down to a
couple components. It's actually being
passed multiple levels through into this
personal banner. So this is going to be
making it hard to reuse things here
because we're always having this logged
in dependency for our our welcome
banner. So with server components, the
best practice would be to actually um
push data fetching down into the
components that's using this and resolve
promises deeper into the tree. And for
this get is authenticated. As long as
this is using either fetch or something
like react cache, we can dduplicate
multiple calls of this and we can just
reuse it anywhere we like inside our
components. So that will be totally fine
to reuse. So now we can actually move
this into the personaliz section here
and we don't not going to need this prop
anymore and just put it directly
oops in here and we're not going to need
to pass this anymore.
And since we're now moving this
asynchronous call to the person section,
we're no longer blocking the page. We
can go ahead and suspend this with just
a simple suspense here. And we're not
going to need this fallback.
As for the welcome banner, I suppose
we're going to do the same.
Um,
but trying to use the get the login
variable here or value, it's doesn't
work, right? Because it's a client
component. So, we need to solve this a
different way. And we're going to do a
pretty smart pattern here to solve this.
We're actually going to go into the
layout and wrap everything here with a O
provider.
So, I'm just going to put this around my
whole app here and get this um logged in
variable over here. And I definitely
don't want to block my whole root
layout. Let's go ahead and remove the
weight here and just pass this down as a
promise into this out provider.
And u this can just contain that
promise. It can just be chilling there
until we're ready to read it. So now we
have this set up.
That means we can actually go ahead and
uh we'll get rid of this prop first of
all and we'll get rid of this one uh
drilling down to the personal banner and
we'll get rid of the prop drilling also
here or the the signature and now we can
use this off provider to fetch this um
logged in value locally inside the
personal banner with use uh off with
that provider we just created and read
it with use. So this will actually work
kind of like in a weight where we need
to suspend this while it's resolving. So
now it just collocated that little small
data fetch inside the personal banner
and I don't have to pass those props
around. And while this is resolving
let's just go ahead and suspend this one
also with a fallback. And let's just do
a general banner over here to avoid any
weird cumulative cumulative shift.
And finally also get rid of this one.
H. So now this welcome banner is
composable. It's reusable. We don't have
any weird props or dependencies in the
homepage. And since we're able to like
reuse this so easy, let's actually go
ahead and add it also to this uh browser
page over here
which will be here. And I can just go
ahead and use it over here without any
dependencies.
So um through these patterns we're able
to maintain good component architecture.
um you utilizing React cache, React use
and make our components more reusable
and composable.
All right, let's tackle the next uh
common challenge
which would be excessive client side
JavaScript and large components with
multiple responsibilities.
Actually uh that's also in the all page
here and again we have to work on this
welcome banner. It's currently a client
component and the reason it's a client
component is because I have this very
simple dismissed state here. Uh I can
just click this. It's it's a nice UI
interaction. That's fine. What's not so
fine though is because of that I
converted this whole component into a
client side component uh or a client
component. And I even used use SWR to
client side fetch. I have now this API
layer here. I don't have type safety
anymore in my data. Yeah, this is not
necessary. And we're also breaking the
separation of concerns here because
we're involving UI logic with data. So
let's go ahead and utilize another smart
pattern to fix this.
It's called the donor pattern.
Basically, what I'm going to do is
extract this into a client side wrapper.
So, let's create a new component here
and let's call it banner container.
And this is going to contain our
interactive logic with this use client
directive. We can create the signature.
We can paste everything we just had
earlier.
And um instead of using these banners,
I'm just going to slot a prop here,
which is going to be the children. So,
this is why it's called a donut pattern.
We're just making this wrapper UI logic
around server rendered content or it
could be server content. And then since
we no longer have this client side
dependency, we can go ahead and remove
the use client. We can use our uh is off
uh asynchronous function here. Instead,
we can make this into an async server
component. Uh we can even replace client
side fetching with serverside fetching.
So let me go ahead and just get the
discount data directly here. discount
data and just utilize our regular mental
model like before
with type safety and that means I can
also delete this API layer that I don't
want to work with anyway
finally for the is loading we can just
export a new welcome banner here with
our donut pattern banner container
containing server render content and
that means we don't need this loading
anymore so we basically refactored this
whole thing into a server component
right and extracted the UI logic What?
But what is that? It looks like I have
another error.
So this is actually uh because of
motion. Do you use motion? It's a really
great animation library, but it requires
the use directive. And again, we don't
have to uh make this use client just for
an animation. We can create again a
donut pattern wrapper
and just extract uh wrappers for these
animations. And that means we don't have
to convert anything here into client
side.
And I'm probably missing something down
here. Yep, there we go.
So now um everything here has been
converted to server. We have the same
interaction. We still have our
interactive logic here, but now we have
this one way to fetch data and we have a
lot less client side.js.
Um
actually I'm using this donor pattern
myself for this UI boundary helper which
looks like this.
Do you see that? So this kind of shows
again what I mean right with the donor
pattern. We have this client component
around a server component. I also marked
a lot of my other components with this
uh UI helper here. Uh also here I have
more server components.
Let's go ahead and uh improve those also
since we're getting pretty good at this
by now. They are in the footer.
These categories um I mean I'm I have
this nice component fetching its own
data and I just wanted to add um this
show more thing. uh just in case it gets
really long. And with a donut pattern, I
can just wrap a show more component
here.
And um this will contain my UI logic.
And it looks like this,
right? Pretty cool. And this is now
containing the client logic allowing us
to use state. We're using the children
count and array to slice this. And
what's so cool here is that these two
are now entirely composable, reusable
components that work together like this.
So this is really the beauty of these
patterns that we're learning here.
Um you can use this for anything. Uh I
also use it for this modal over here.
Yeah, just remember this next time
you're considering adding any sort of
client logic logic to your server
components.
Okay, we know the dot pattern. uh we
don't have to utilize it to uh uh create
these composable components and avoid
plans.js. So we can move further to the
final issue.
Let me go ahead and close this again.
So that would be um with lack of static
rendering strategies. Right?
Looking at my built output, I actually
have every single page be a dynamic page
here. So that means that whenever I load
something here, this is going to be
running for every single user. Sorry.
Every single user that opens this is
going to get this loading state. It's
going to be wasting server costs, making
the performance worse.
And that means also that something
inside my pages is causing dynamic
rendering or forcing dynamic rendering
for all of my pages.
Um, actually it's inside my root layout.
I don't know if you experienced this.
It's over here. Uh, in my header I have
this user profile. And this is of course
using cookies to get the current user.
And that means that everything else is
also dynamic rendered because again
pages could be either dynamic or static,
right? This is a pretty common problem
and something that has been solved
before in previous versions of next. So
let's just see what we might do.
One thing we could do is create like a
route group and split our app into like
static and dynamic um sections. That
would allow me to extract my about page.
I could render this statically.
It's okay for some apps, but in my case,
the important pages is the product page,
and this is still dynamic. So, not
really helpful.
How about this uh strategy? So, here I'm
creating this request context param
encoding a certain state into my URL and
then I can use uh generate static params
to generate all of the different
variants of my pages. That would
actually combined with client side
fetching the user data allow me to get
this cache hit on my product page.
definitely a viable pattern. It's
recommended by um the versel flags SDK
called the premputee pattern I think.
But this is really complex and I have
like multiple ways to fetch data and
actually I don't want to rewrite rewrite
my whole app into this. So what if we
didn't have to do any of those
workarounds? What if there was a simpler
way? Well, there is. Let's get back to
this uh our application again. So we can
actually go to the next config and just
enable cache components.
Oh, nice. Okay. And what this does, as
you know from the keynote, um will
actually opt all of our uh asynchronous
calls into request time or dynamic. And
it will also give us errors whenever we
have some asynchronous call not
suspended. And it will give us this use
cache directive that we can use to
granularly uh uh cache either a page, a
function or a component.
So yeah, let's go ahead and and utilize
this. We can begin with the homepage
here.
Let's have a look. So again, I have this
mix of static and dynamic content. I
have my welcome banner for me. Uh
something for you also for me. Um let's
have a look at that with this this UI
helper again. So for example, the banner
is dynamically rendered right with this
over here. Whereas I marked this as high
rendering because the hero it's it's
fetching this asynchronous thing and
it's going pretty slowly but it doesn't
depend on any sort of user data or
dynamic APIs. So that means that
everything that is hybrid rendered here
can actually be reused across requests
and across users and we can use the use
cache directive on that. So let's add
the use cache directive here
and mark this as cached.
And that will allow me to whenever I
reload this page
um
I didn't save this. There we go. It will
not reload this part, right? Because
it's cached. It's just it's now static,
right?
And there's also other uh related APIs
like um the cache tag to allow me to tag
this or validate the specific uh cache
entry uh granularly or define my reation
period. But for this demo, let's just
focus on the the plane directive. Now
that I have this used cache directive, I
can actually remove my suspense boundary
around this hero.
And that means um well what this will do
is that uh partial pre-rendering can
actually go ahead and include this in
the statically pre-rendered uh shell so
that uh this hero will in this case be a
part of my build output. Let's do the
same thing for everything else on this
page that can be uh shared. For example,
I have this feature categories over
here. Let's go ahead and do the same
there and add the use cache directive
and mark this as cached
like that. And we can remove the
suspense boundary. We're not going to
need this anymore.
Same for the feature products. Let's add
use cache and mark this as cached.
Oops.
And then remove the suspense boundary.
So notice how much complexity I'm just
able to remove here. I don't have to
worry about my skeletons, my cumulative
layout shift that I was doing before.
And the page is no longer uh or we don't
no longer have this page level static
versus dynamic uh limitation.
So now when I load this page, you'll see
everything here is cached except for
this truly user specific content,
right?
So that's pretty cool. Let's go to the
browse page and do the same thing over
there.
Um yeah, I already marked all of my
boundaries here so you can easily
understand what's happening. And I want
to at least cache um these categories.
Looks like I'm getting an error though.
Maybe you recognize this. So, it means I
have a blocking route and I don't I'm
not using suspense boundary when I
should be doing it. Refreshing this.
Huh, that's true, huh? This is really
slow and it's causing performance issues
and bad UX. So, this is great. Use cache
or cache components is helping me
identify my blocking rods. Let's
actually see what's happening inside
that. So, this is this is the problem,
right? I'm fetching this categories top
level and I don't have any suspense
boundary above it. Uh, basically we need
to make a choice. Either we add a
suspense boundary above or we opt into
caching. Let's do the simple thing first
and just add a loading tsx here.
And let's add a loading page over here.
Uh some nice skeleton UI.
Uh that's pretty good. It resolved the
error, but I don't have anything useful
happening on this page while I'm
waiting. I can't even search. So with
cache components, um dynamic is like or
static versus dynamic is like a scale.
And it's up to us to decide how much
static we want in our pages. So let's
shift this page more towards static. And
just delete this loading tsx again.
And then utilize the patterns that we
were learning earlier to push this data
fetch into the component and collocate
it with with the UI. So move this down
into my responsive uh category filters
here. I have two because responsive
responsive design. Uh I can actually go
ahead and uh just add it here. Oops.
and import this. I don't need this prop
anymore. Actually, my component is
becoming more composable.
And instead of suspending it, let's just
add the used cache directive.
And that should be enough. So, notice
how I'm being forced to think more about
where I'm resolving my promises. Uh, and
actually making improving my component
architecture through this. I don't need
to suspend this. This will just be
included in the in the static shell
here.
Um, the product list. Let me just keep
this fresh.
H so I can reload that every reload that
every time. Whereas the categories at
the bottom.
I also want to cach this. So let's go
ahead and go to the footer.
And uh since I'm using the donut pattern
over here, this can actually be cached
even though it's inside this UI uh part
of the UI that's interactive. So this is
totally fine. So that pattern was not
only good for composition but also for
caching.
Um I think I have one more error there.
Let's see what that is.
Still have this error. This is actually
because of these search brands. Search
brands as we know is a dynamic API. I I
can't cach this but I can resolve it
deeper down to reveal more of my UI and
make it static. So let's go ahead and
move this down. Pass it down as a
promise
to the product list. We'll make this
typed as a promise over here
like that.
Um let's resolve it inside the product
list. Use the resolved search params
over here and over here. And since this
is suspended here, the error will be
gone. So reloading this,
the only thing that's that's reloading
here is just the part that I picked
specifically to be dynamic. Everything
else can be cached. And that means that
I can interact with my banner or even
search uh because that part has been
already pre-rendered.
All right, let's do the final uh page
here, which is the product page, which
is um the most difficult and the most
important one. It's It's really bad
right now. This is like super important
for an e-commerce platform, apparently.
All right. Um let's go ahead and fix
that one, too.
So, here I have this product page. Uh
let's start caching just the reusable
content here, for example, the product
itself. and just add use cache here and
mark this as cached.
That should be fine. That means we can
remove the suspense boundary over here.
Uh all right. And this is no longer
reloading on every request here. Right.
For the product details, let's do the
same thing. Let's add use cache. Let's
mark it as cached
and see if that will also work.
It did not. Uh actually, this is a
different error. It's telling me that
I'm trying to use dynamic APIs inside of
this cacheed uh segment. And that is
true. I'm using the save product button,
right? That allowing to click and toggle
the saved state. So, what do you think
we can do with this?
We can use a donut pattern again.
Actually, we can also slot in dynamic
segments into cache segments. So, we're
interle them just like before, but with
cache. So, this is pretty cool. Let's go
ahead and add the children here.
Um, like that.
And this will remove the error. And I
can just wrap this around this one
dynamic segment of my page here. Remove
the suspense boundary. Um, and add in
just a very small bookmark UI for that
one dynamic piece of the page.
And let's see how well that looks now.
So notice how almost the entire UI is
available, but I have this one small
chunk that is dynamic. And that's fine.
Everything else is still there. And
let's leave the reviews dynamic because
we could keep those those fresh. There's
still one more error. Let's just quickly
tackle that. Um again, this is the
params. I'm getting help uh that I need
to make a choice either add a loading
fallback or um cache this. Let's just
use generate static params in this case.
Kind of depends on your your use case
and your data set. But for this case,
I'm just going to add a couple
pre-rendered predefined pages and then
just cach the rest after generated by
users. And this will remove my error
here.
So I think I'm actually done with my
refactor. Let's go ahead and have a look
at the deployed version and see what
that looks like. So I just deploy this
on our cell.
Um and
remember I purposely slow down a lot of
data data fishes here. And still when I
load this page initially it's just
everything is just available already,
right? The only thing here is just those
few dynamic segments like the discount
and the for you. Same with the browse
all all of the UI is already available
and for the product itself it just feels
instant right and remember again that
all of these cache segments will be
included with uh the static shell with
partial perending and it can be
prefetched using the improved
prefetching in the new next 16 client
router. So that means that every
navigation just it just feels so fast,
right?
Um all right to summarize um with cache
components there is no more static
versus dynamic
um and we don't need to be avoiding
dynamic APIs or um compromising dynamic
content and we can skip these complex
hacks and workarounds uh using multiple
data fetching strategies just for that
one stat this cache hit uh as I showed
you. So in modern XJS dynamic versus
static is a scale and we decide how much
static we want in our apps and as long
as we follow certain patterns uh we can
have one mental model which is
performant composable and scalable by
default. So let's get back to the
slides. So if you're were not already
impressed by the speed of that this is
the lighthouse score. So I collected
some field data with versel speed
insights. So we have a underscore on all
of the most important pages the homepage
the product page and the product list
even though they are highly dynamic. So
let's just finally summarize the
patterns will that will ensure
scalability and performance in nextg
apps and allow us to take advantage of
the latest innovations and get scores
like this.
So firstly we can refine our
architecture by resolving promises deep
in the component tree and fetching data
locally inside components using react
cache to do duplicate work. We can avoid
excessive prop pass into client
components by using context providers
combined with react use.
Second, we can compose serving client
components using the donut pattern to
reduce client side JavaScript, keep a
clear separation of concerns and allow
for component reuse. And this pattern
will further enable us to compose um to
cache our composer components later. And
finally, we can pack cache and
pre-render with use cache either by
page, component, or function to
eliminate redundant processing, boost
performance and SEO, and let partial
pre-rendering statically render these
segments of the app. And if our content
is truly dynamic, we can suspend it with
appropriate loading fallbacks.
And remember that all of this is
connected. So the better your
architecture, the easier it is to
compose and the easier it will be to
cache and preend with the best results.
For example, resolving dynamic APIs
deeper in the tree will allow you to
create a bigger partially printed static
shell.
And with that, this is the repo of the
completed version of the application.
There's like so many so many things I
didn't even show in there that you can
check out and you can scan the QR code
uh to find my socials there alongside
the repo if if you don't want to take a
picture and type it in yourself.
So yeah, that's it for me. Thank you
NextJSC for having me here.
[Music]
Deep dive into the Next.js App Router, covering React Server Components, modern routing, and caching techniques you can apply directly to real projects. Get a demo today: https://vercel.com/contact/sales/demo