Styling active links in next.js is tricker than you think

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
a common navigation UI pattern is showing some kind of visual indicator representing the page that a person's currently on but if I wanted to do that inside of a nextjs app it should be as simple as just grabbing the path name inside of a server component right well after not finding any nextjs apis that support that on the server I took to Twitter I got some links to GitHub and I was able to figure out how to actually get the path name on the server but it might not be what you think so let's see how we can style our active links inside of a nextjs app digging into the site I have this simple application that just show some of the movies and TV shows that I currently watched and I have these different pages where I have all which is just a mix of both I have the movies and I have the TV shows and what I want to happen is anytime somebody clicks one of these links inside of this nested layout group I want to be able to show some kind of visual indicator letting them know that they're on that specific page looking at the code I'm not doing anything overly complex I have my homepage a layout that represents the basic structure but then I also have this watch directory which includes all those pages that I just showed including its own custom nest to layout which includes a sidebar that lists out all those links now I already have some Styles set up where it's just some basic Tailwind Styles where anytime a link is active I want to show it with some white text and a blue background instead of just the standard black text now of course I don't want all the links to be blue but this is just an example of what it will look like when somebody actually clicks on one of those links now going back to the code the problem is how do we determine which link is actually active if I head over to the next JS docs I can search for path name which comes up with the linking and navigating page where if I search for a path name again and start to go through we can see that I have this used path name hook available but as we can see here with the used client directive it means that it needs to be inside of a client component but I'm working inside of a server component so how can I actually grab that value on the server rather than opting in this entire tree into a client component one of the links that my buddy Usman shared on Twitter was this solution from ljos where we can actually use middleware in order to grab the current request URL and specifically the path name we can store that inside inside of a header where we can then access that header inside of the component now if you're not familiar with the concept of middleware it gives you the ability to run some kind of code before a request is actually completed one of the examples that the nextjs doc show are being able to add a redirect depending on the page but some other examples are things like authentication to make sure that the requests that are coming in are properly authenticated but for our use case we want to just grab the path name of our request so I'm going to go ahead and just copy this code AS is now heading to the code I want to create a middleware TTS or JS file next to the app directory so in my instance I'm currently using a source directory which nests the app directory so inside of source I'm going to create middle wear. TS and inside I'm going to go ahead and paste all that code now before we move forward let's actually break this down and see what's actually happening where inside of our middleware function anytime that this is executed we're going to grab the request headers where we're going to explicitly set a header of X path name X next PATH name which is going to be make sure I'm hidden here the request next URL and then that path name where then we're going to take that and we're going to just return the headers and modify the request response so that it pass along those new headers now if we look at the bottom here we can also see that we have a configuration with a matcher which is just going to try to match all the different requests that we actually want to modify with this middleware so now that we have the middleware set up let's actually grab that value from inside of our component so similarly I'm going to go down to this layout. TSX file example that he has where we can see that we're going to use the header module in order to actually grab the path name so first I'm going to copy this import and I'm going to paste it in the top of my file but then I'm going to create this new con that's going to represent my path name and pasting this actually inside of my component we can see that we're going to grab the headers and then we're going to get specifically the X the X next PATH name value so now let's actually console log this out just to check if it's actually working if I open up the browser and I refresh the page I'm going to go ahead and hide myself and we can look inside of our terminal here and we can see that I get path name and I do indeed get that slw watched sltv so moving back to my code I can start to use this and I can say inside of here I'm going to actually create a cons is active and says if path name equals link. path then I can say I want to add those Styles and this works great we can see that I have my active link but and there's always a but let me try clicking over to movies we can see that the page changed but I no longer have a different active state for my links if we look back inside the terminal hiding myself again I'm trying to see if I can find that path name and looking up I still only get that original slw watched sltv path name so what's actually going on the problem is this layout file only ever renders that first request anytime I'm navigating to those other Pages inside of the browser that's all happening client side so that layout never renders and that server request never runs giving me that path name we can even see that once I'm on the movies page or that watch page if I refresh the page again that's going to give me that initial server request so I do have that active link but if I again client side browse to another link I'm no longer going to have that different active State and I think this is an important point to illustrate with react server components at least in the context of nextjs where every single page that we hit isn't going to be one big server request so we're not going to rerender that layout on every single page and it's doing that from an optimization perspective and giving us a persistent layout throughout that so we can really load in the dynamic bits inside of the browser but this really makes us kind of rethink how we're structuring our applications but don't take it from me Le Rob himself even comment on this right and said this is probably not the recommended way to do it so how can we actually still grab this value and if we don't need to do it on the server or if we can't do it on the server how can we do it on the client and if we are doing it on the client how do we do it in a way that doesn't impact the rest of the tree of components now in order to do this on the client we saw this a little bit earlier where we can use the use path name hook and it's as simple as that we're going to use this hook and we're going to be able to get the path name which we can use to actively style it now let's just get this working in the first place so I'm going to go ahead and turn this entire layout into a client component and just as a caveat here of course client components aren't bad but ideally you would want to have a server component if you can right so we have our use client directive here which is going to turn this entire tree we'll get back to that later but then I'm going to import use path name from next navigation then inside of the component itself I'm going to change this from path name to use path name and I'm going to get rid of my async designation here just to make sure that I'm not trying to load an async component inside of client component because I don't think that still works but we have our path name and now that we have that it should still just work as I currently have it and if I head back to the browser and try to navigate around it's working perfectly so we have everything that we need and it's actively styling those links exactly how I want them but how can we do this without impacting the rest of that tree now going back to the code there's really no reason for me to to have to opt in that entire tree of components just to style some links a little bit differently now fortunately I can actually use client components inside of a server component so what I can do is abstract the sidebar out allowing me to only opt in that one component into a client component and that won't have an impact on the rest of that tree so I'm going to start off by creating a new component and I'm going to call that sidebar. TSX and ignore that that's not currently following the same folder file structure just doing this for example but I'm going to then export a default function I'm going to call that sidebar and that sidebar is going to take some links and we'll get to that in a second but ultimately I'm going to want to return the code where I'm going to go back into my layout I'm going to grab that entire unordered list and I'm going to go ahead and paste in the code and this code's not necessarily important it's really just looping through the links and creating a link for every single one but what is important is now I need to add an interface spelling right for sidebar props and inside I'm going to have my links which is going to be an array that each contain a label which will be a string and a path now of course I'm going to need to add those sidebar props as my type and we should start to see some of those type issues go away I need to also import the link component from next link I'm going to go ahead and add myself back in here but we can start to see what else I need I also need my CN utility from at l utils what else and we can see we do need that path name still so I'm going to go ahead and grab that as well from our Ed path name hook make sure that I grab that over as well and of course I need to get rid of this console log and then ultimately we need this to be a client component so I'm going to add use client to the top of this component because we're opting in only this component as a client component but now I think I got rid of all the red issues yeah so let's head back over here I can now get rid of those original Imports I can now import my sidebar from at components sidebar and now I should be able to take these same links scroll down render in my sidebar and then pass in my links as links now of course we have one more thing and that's at the top of our layout file let's get rid of that used client where if we head back to the browser now we can see that if we start to click around everything is still working as expected only this time the inside content is being loaded as server components and the sidebar is going to be its own little client component now I know the solution kind of felt like styling active links with extra steps where we definitely didn't need to go into middleware but I think it's important to show the implications of the server rendering and how we don't necessarily have access to the things that we might traditionally think we have when we're communicating between both the server and the client now UI patterns like these are useful for creating good ux for the people on your page next up let's see how we can add smooth scrolling to links and other elements inside of react
Info
Channel: Colby Fayock
Views: 811
Rating: undefined out of 5
Keywords: style active link nextjs, nextjs active link, nextjs pathname, nextjs path, nextjs layout, next js layout tutorial, next js layout page, next js layout component, nextjs ssr, nextjs usepathname, nextjs current page path, nextjs layout pathname, nextjs middleware, next js headers, nextjs set headers
Id: OZRGQeAp8Uw
Channel Id: undefined
Length: 10min 2sec (602 seconds)
Published: Thu Feb 22 2024
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.