Migrating a Headless WP Next.js Website from Pages Router to App Router

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
what I have here is a nextjs Blog website built using the old Pages router and I want to move it entirely to the new app router let's see how we can do that so here within the Pages directory I have the blog post route file the blog index page category page contact page and the homepage by the way this was the default homepage which I had forgotten to delete so let me just get rid of that okay before beginning let me start the development server by going to the terminal and running the command nbm run Dev you can see that currently we are using nextjs version 13.5 which already supports the app router here is the development website opened in the browser which is currently running off of the Pages directory let's start by moving the single post page route which is a dynamic route the first thing we need to do is to create a new folder called app in the same level as Pages you can have both pages and app directories at the same time no issues with that next create a layout. JS file which is the root layout file this root layout file is going to contain our sdml and body tags which replaces the previous underscore app and underscore document files we can also import the CSS files at the top the root layout file exports a function component by the name root layout it gets the child components as [Music] argument add the HTML and mod tags and place the children within the body tag overall the layout file gives you the freedom to structure your HTML layout the way you like you can add attributes and classes as required I'm going to begin by moving the blog post page so create a directory called blog within which create another directory called post slug wrap it inside square brackets to denote that it's a dynamic route and inside that create a file called page.js page is a reserved file name in the new app router within which we Define the page component also compared to the old Pages router there's an extra level of folder in the app router now I'm going to copy everything from Pages blog post Slug and paste it within the new file that is page.js file within the app directory but it's not going to work just like that if you refresh the page we should see an error maybe I want to restart the development server npm run Dev again now go to the browser again and you can see the error conflicting app and page file was found that means you cannot have the same route file inside both the pages and the app directories so I'm deleting the one in the Pages directory let me restart the develop server again but now we have another error get static props is not supported in the app directory that means in the app router there is no need to use functions like get static props or get server side props in the pages router when you wanted to fetch data for a statically generated route do that inside the function get static props the fetched data will be there until you revalidate the path or rebuild the site on the other hand if you want wanted to fetch data for a server s side rendered route do that inside the function get server s side props server side rendering means fresh data is fetched upon each request and any data returned by either of these functions will be available to the main page component function as arguments or props since both of these function get executed only on the server side you can be sure that the data fetching happens on the server only not on the client device coming to the app router you no longer need to depend on these two functions to fetch data on the server you can use the JavaScript fetch function directly inside the component function the fetching will still happen on the server only because all components are server components by default in the new app router next JS has also extended the native fetch function with additional options like revalidate and cach a and by default all data fetches are cached for Infinity which makes the route effectively a statically generated route on the other hand if you want it to be a server s side rendered route set the cach a option to no store and that makes it equivalent to using the function get server side props with that I hope the idea is clear so we can move on before beginning to correct make sure to update the nbm packages to the latest versions run the command nbm update which updates all the things within the package Json file including nextjs and other dependencies nextjs is constantly fixing bugs and making changes in the newest versions so I'm just ensuring that we are using the latest version available as I'm recording this video okay the update is successful by the way way we were using Tailwind to style the website so we want to add the new app directory to the Tailwind config file so that Tailwind keeps watching for file changes and rebuild the stylesheets okay the update is complete and now I am restarting the development server currently we are using version 13.5.3 now we want to fix this error regarding that static props so I am going back to the page.js file where I I want to make the necessary changes first of all I want to fix the directory paths for the imported components now coming to the get static props function things like post data comment data and SEO data were fetched from within get static props and exported as props which then becomes available to the default function post now we long no longer need to do this inside get static props we can do that directly within the page component that is function Post in order to use a weit we can make the function a sync I'm not sure if that's the correct way but anyway it works in the app router all components are server components by default so there is no problem in fetching data from within the page component and since it's a server component this component will not be bundled to the client so it's safe to perform data fetching and API calls the function post also gets the parms property as an argument now that we have moved everything inside get static props we can safely delete this function within the return return statement of the get static props function I was making some modification to the featured image URL string so I want to do the same here as well the post data contains only the featured image URL but to use it as an inline CSS value we have to wrap it inside the CSS URL function like this so featured image URL equals URL of featured image URL okay now we no longer need this function get static props so I'm just deleting that next we need to change the get static paths function in the app router the name of the function becomes generate static params not only that but the structure of the returned property has also changed now there is no need to put each path object inside the params property you can return the array of path values directly post slug s do slug the map function creates an array for all the values within the post slugs constant now return paths okay that's all about the function generate static params you can see that it's much simpler than the previous get static paths function moving on to the next area that is the head component within which we were dealing with the SEO data we no longer need to do it inside the head component instead the app router has built-in support for exporting a C data you can export a function called generate metad data the function returns an object which contains all the SEO data as key value payers the function generate metadata is used to generate SEO data dynamically that means you can fetch SE data from an external source and return an object with the SEO data as key value payers here I am fetching the data by calling the function get SEO o now we can return an object which contains the properties like title description open graph tags Etc starting with the page title whose value is SEO data. title then comes the description SEO data dot description that is metad disk we can use the question mark notation to check if the value actually exists or not within SEO data now comes the open graph values by the way we are expecting these values to be returned by the WordPress graphql API there I had already set up the Y SEO plugin which already sets up the open graph tags for all the posts and Pages that's why we are able to retrieve it here I'm not going to add all the open graph tags I hope this is enough to give you the idea we are adding the title description locate and site name the point to keep in mind is that here we are using the built-in nextjs function generate metadata to set up the SEO tag values the values returned by the generate metadata function will be inserted into the head tag by nextjs during build time or render time so we no longer need to do it manually via the head component that's the difference also if you have only some static values as SEO data there is no need to use the function generate metadata instead you can export a constant called metadata the constant metadata must also be an object which contains values like title description open graph tyes Etc but it's only useful if you want to hard code some static values in most other cases you might want to use the function generate metadata instead one more thing we need to deal here is the Json LD schema markup we had already fetched it above so I'm just cutting it and pasting it outside the head component now we are ready to delete the head component all together you can also remove the import head at the top now if we go to the site and reload you can see that the error says you are importing a component that needs use state but features like use State and use effect cannot be used within a server component you need a client component for that the difference between server components and client components is a matter on its own so I'm not going into the details of that in this video just keep in mind that server components render only on the server and are not interactive client components are also rendered on the server but they are also hydrated on the client to make the them interactive that's why you cannot use States and defs inside a server component whereas within a client component you can in our case I was using State variables to store the state of our command form so let me go to the command form component to convert it into a client component add the directive use client at the top of the file now it's a client component and the error is gone let me quickly verify if everything is working correctly or not opening the inspect element tool going to the Head area you can already see the SEO data exported by the generate metadata function everything is working title description open graph tags and Twitter cards in order to make sure it is actually rendered by the new app router let me add a line to the page.js file H2 rendered by the new new app router and you can see the line at the top so that confirms that the page is rendered by the app router and not the old Pages router the next page I want to migrate is the blog index page here is the index.js file within the pages router we want to migrate it to the app directory taking a quick look at the blog index page this is how it looks like just like we did for the previous route I'm going to copy everything from this file and put it inside the app router and for the blog index route I want to create another page.js file right inside the blog directory and paste everything into that then delete the index.js file from the Pages directory now the blog directory contains nothing so we can remove the entire directory itself on the web page we got the same error like before you cannot use State inside a server component and on this page we were using State variables to store the list of posts when the user clicks the loot more button the state value gets changed with the newly fetched posts however function blog home is the page component and it needs to be a server component so we cannot just simply add the use client directive at the top to convert it into a client component because certain features like generate metadata or server only and it works only inside a server component if you look here you can see that we are using State variables to store the list of posts and display them inside this list tag so what we can do is create a new component then move this whole main tag into that then we can mark it as a client component and use the state variables so inside the components directory create a new file called post list.js which exports a function component called post list then paste all the main tag within the return statement now back in the page component we no longer need many of these imported components like lot more date featured image Etc because we have already moved all of those into the new component so get rid of that this One Import use state is also not required so cut all of that and paste it inside the post list component also move the state variable initiation to the post list component notice that here we are setting the initial set of posts by calling the use State function in the pages router we were doing that within the get static props function coming to this new setup there are multiple ways to handle the initial data fetching either you can perform it right within the function post list or you can fetch it within the parent server component and pass it as a prop that's what I'm going to do here so add a prop called initial posts to the function post list then set it as the post State variables initial value back to the page component we also want to move the import link statement to the new component the link component is used to link to the individual post Pages again going back to the page component we want to fetch the initial set of posts we are already doing that within the get static props function as constant all posts change the function argument to params then Define another constant called initial posts because here we are fetching the initial set of posts not all posts then assign its value to the function call await get post list also don't forget to add the async keyword in front of function now get rid of the function get static props then at the same location where the main tag previously existed call the Post list component and pass the initial posts as a prop and to do that we need to import the component post list to the current file that is page.js import post list from components SL poost list okay now we have another error the same error we saw before because I had forgotten to Mark the component post list as a client component to fix that I need to add the directive use client at the top of the file now the component post list is a cent component there are some file path errors also let me fix that as well okay now the blog index page is working correctly next I want to add some SEO data that is title and description since title and description are static for the blog homepage there is no need to use the function generate metadata instead we can export the constant metadata with hardcoded title and description values title blog description read our latest blog posts okay the title is showing up on the tab one more thing since we are exporting metadata there is no need for this head component here so delete that with that our blog index page is also successfully migrated now comes the category page that is the category archive pages category archive is a dynamic route within which we were displaying posts from a particular category the route path looks like category SL category name so create two directories category and within that create another directory called category name wrap it inside square brackets to denote that it's a dynamic route and inside that create a file called page.js then copy everything from the old file to to the new file then make all the required changes like we did for the previous two routes like in the previous blog index route here also we are using State variables to store the list of posts and display them within the main tag so we need to replace that with the post list component we created just before import post list at the top then move the data fetching part out of the function get static props here we were fetching the list of posts in a category as well as the details of the category take both of them and put it inside the main function function category archive delete the state variable declaration add the async keyword also change the props delete the main tag and replace that with the post list component then pass the category posts as the initial posts the post list component will in turn load the load more component which fetches the next set of posts updates the state variable ET when the user clicks the lot more button also call the site footer component at the bottom then delete the get static props function then we need to change the get static paths function to generate static params within this function we are fetching the list of category slugs and returning it as an array so constant paths contains the array of category slugs let's create the array by mapping the values of the constant categories the name of the route folder is category name so the object looks like category name category do slug okay now return constant paths then delete the unnecessary import statements delete the head component looking at the web page there's an error regarding conflicting route files and that's because I had forgotten to delete the category name route from the Pages directory so delete that and now the error is gone fix the the incorrect paths as well okay now we are able to view the individual category archive Pages like destinations inspiration Etc the next route we want to move is the page slug route this route is responsible for rendering static pages like the about page terms and conditions page Etc again copy everything from the file then delete the file then go to the app directory create a new directory called page slug within square brackets then inside that create a new file called page.js paste everything from the clipboard this is going to be repetitive so let me just fast forward with that pages are also migrated here is the about page privacy policy page and the sample page the next item on our list is the contact page route that is contact. JS this route is a little more complex because it involves form submission handling and things like that so we need to handle it accordingly once again start by copying everything from the contact JS file and move it to the app directory new folder contact then page.js and paste everything here we have the submission alert component which displays the message when a form is submitted as well as the handle submit function which handles the form submission event since we cannot use State variables within a server component we need to move everything to a separate component and Mark it as a client component so what I'm going to do is get everything inside this section tag and move it to a separate component inside the components folder create another component called contact form. JS export default function contact form then paste that inside the return statement in addition to that we also need to move the submission alert component which means we have two components within the same file one is the contact form itself and the submission alert component okay paste that inside contact form. JS then comes the Handler function cut everything including the state variables and the handle submit function definition then paste that inside the function contact form okay then import use state from react at the top also Mark the component as a client component use client then going back to the contact page component import contact form also correct the paths remove the head component then below the site header component call the contact form component followed by the site footer component okay the contact page along with the form is showing up correctly but we haven't checked whether it's working correctly or not the form data is being submitted to an API route at the address API SL form so we need to move that API route as well to the app router before checking by the way we need to add the metadata title to the contact page so export constant metadata with the title contact us okay maybe some description as well go contact as to no more details okay before moving on to the API routes we have one more page route remaining to be migrated and that's the sites homepage itself it's just a static page without any Dynamic data so it is easy to migrate create a new file called page.js right within the app directory then make the adjustments okay here is the homepage and with that we have successfully migrated all the page routs since there are no more pages within the Pages directory we can delete the app.js and document. JS files also coming to the API routes we have three routes to be migrated command form and revalidate taking a look at the old style within the pages router an API route exported a function by the name Handler with two arguments request and response the function returns a response object with an optional status code and the message now this style has completely changed in the new app router so there is no point in copying this code and making modifications so I'm going to delete all of them from the Pages directory and create them a fresh inside the app directory in the new app router you can create API routes within any directory not just inside the API folder also the ter API route is renamed to Route handlers which sounds more generic the notable difference between a normal page route and a route Handler is in the file name a normal page route is created within a file named page.js whereas a route Handler is created within a file called route. JS a route. JS file can be placed inside any directory however you cannot place a route file alongside a page file within the same directory because that results in a routing conflict apart from that there aren't much restrictions in our case I want to preserve the URL paths just like they were inside the pages router so I'm going to put all the route Handler files within a folder called API so create a folder called API inside that create three more folders comment form and revalidate and each of those folders will contain a route. JS file also keep in mind that route. JS is also a reserved file name like page.js let's start with the comment form submission route now if you try to submit a comment we will get an error submit and there is an error the error occurs because the comment data gets submitted to a route called API comment which is not defined yet so go to comment route. JS first of all we need to import the next response object from next SL server the next response object is an extended version of the JavaScript response object object in addition to that we also need to import the create comment function from Commons this function creates a graphql mutation to create a new comment within our WordPress [Music] backend then export a function component export a sync function post it accepts the request as an argument here notice that we have named the function as post because the data is being submitted from the front end as a post request so in the app router the name of the route Handler function denotes the HTTP request type apart from post you can also create functions named get put patch delete Etc inside the function we want to fetch the data from the request body so constant body equals request. Json by the way request is an instance of the class next request which is also an extended version of the Native JavaScript request object available as part of the fetch API we should be able to see how it looks like by outputting the body to the console also the function needs to return an instance of the class next response with the message comment submitted let's try TR submitting a comment and we should be able to get the response back name Abino email abow test.com with some message testing click submit and we got the green response comment submitted which means its status is 200 okay you can see the same inside the browser console as well we had also outputed the request body to the server console so let's check that as well where is it uh it still says promise pending that's because the request. Json function returns a promise so I need to add the await keyword in front of the function call now let me try submitting the form again and this time we got the request body with all the data we submitted from the form or order email content and post ID now that we have the submitted data within the request body we can send that to the WordPress graphql API as a mutation we have already defined a function for that function create comment so just call that await create comment and pass the body if there are any errors return an error status with the status code 400 which means means bad request otherwise send a 200 Response Code saying your comment is awaiting approval behind the scenes the create comment function is building the mutation and passing it to the graphql request function which in turn sends everything to the WordPress graphql endpoint for more robust error handling you might want to put all these function calls inside try catch block but for now I hope this is enough you got the idea if it's neither error nor success we can send a 500 error code which means server error internal server error finally let me try submitting another comment name email message and then hit submit but this time we got a red error box saying sorry this post is closed to comments at the moment but don't worry about that it's the expected Behavior because I had closed comments from the WordPress admin that's what the error says okay so with that we have successfully moved the first route Handler the next one we want to move is the form route this is the endpoint to which our contact form gets submitted it's mostly similar to the comment route so I think I can fast forward the function just checks if all the fields are present or not and returns a response accordingly on a real contact form you might want to validate the details submitted store them to a database or send notification emails also in the contact form component I'm changing the result key from data to message okay now let me try submitting the contact form I'm leaving the email field then click submit and the response says all Fields required fill that as well then submit again and this time we got a success response so that's working and the last route we want to move is the revalidation route this is the route that handles revalidation requests from the WordPress back end so whenever a post is created or updated from the WordPress back end WordPress notifies our nextjs server and in turn nextjs needs to fetch the fresh data from from the WordPress backend that is the graphql API endpoint and updates the pages requested Pages again starting by importing the next response object from next SLS server then we also want to import the revalidate path from next cach a the request sent by the WordPress Bend is going to be a get request so we want to name the function as get the request will contain the type and the secret as URL parameters so we can access them from the next URL search params property the type variable tells us whether it's a post page or the homepage that is requesting revalidation so we can use a switch condition to check the value of the variable type if it's a post that means WordPress is requesting next JS to revalidate blog posts if that's the case then the path to be revalidated is /blog SL poost slug that's the dynamic route for a blog post page this is a little different from the revalidation mechanism in the pages router where we could specify the exact block post slug to be revalidated whereas in the app router we are setting the route path that is the dynamic route path that represents all blog posts here the downside is that it will try to revalidate all blog posts even if only one post is updated from the back end so I prefer the old revalidation mechanism where I could specify exactly which post I want to revalidate by its slug however there are workarounds like you can specify tags to identify individual posts like that but that's more work so let's keep it like that and move ahead the next case is page so whenever a WordPress static page is updated the path to be revalidated is slash page slug that's the dynamic route path for a static page finally when the homepage is updated we want to revalidate the /blog route now that we have the path to be revalidated next we need to verify the secret so check if the secret send along with the request matches the one stored in the environment variable if it does not match send an error response invalid token with status code 400 otherwise call the revalidate path method and pass the path value then return next response with the Json values revalidated set to true path the path that's just updated along with the current time in case something goes wrong send a 400 error code okay now let me go to postman to try out the revalidate route try sending a request with an invalid secret key and we get the error message invalid token with status 400 bad request now correct the secret key and this time it got revalidated so with that we have successfully moved everything from the pages router to the app router I hope you found this video useful thanks for watching
Info
Channel: Coding Reflections
Views: 1,627
Rating: undefined out of 5
Keywords: next js tutorial, app router, pages router, next js app router, migrate from pages router to app router
Id: D0WPScvcKYo
Channel Id: undefined
Length: 43min 26sec (2606 seconds)
Published: Tue Oct 03 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.