Design Converter
Education
Senior Software Engineer
Last updated on Jun 3, 2024
Last updated on May 2, 2024
Welcome to the world of Next.js, where the ease of creating user-friendly web applications meets the power of a robust JavaScript framework. Next.js has transformed the way developers create React applications by providing a unified development experience through capabilities such as server-side rendering, static site creation, and, of course, its easy routing system.
In this blog post, we'll dive deep into the heart of Next.js—its file system-based router and the powerful custom App component.
Next.js has revolutionized the way we think about routing in web applications. Its file-based routing system is a testament to its commitment to convention over configuration, making the process of creating and managing routes straightforward and intuitive.
The introduction of the app directory alongside the traditional ‘pages’ directory further enhances this routing system by organizing routes, creating nested routes and layouts, and facilitating the incremental adoption of the new App Router built on React Server Components. This dual-directory approach underscores the importance of a well-organized folder structure in route creation, allowing for the colocation of files, route groups, and the elimination of path redundancies.
Within this system, the 'pages' folder plays a crucial role in efficient route management, serving as the primary directory for setting up and managing routes, thereby streamlining the transition between routing mechanisms and ensuring a seamless user experience.
At the heart of Next.js lies the file system-based router, a feature that leverages the pages directory to generate routes for your Next.js app automatically. The concept is simple: the file structure within the pages directory corresponds directly to the route paths in your application. This means that if you create a file named about.tsx in the pages directory, Next.js will automatically make it accessible via the /about URL path.
Dynamic routes add a layer of flexibility to your application, allowing you to define variable route segments that can change based on user input or other data. In Next.js, dynamic routes are created by adding square brackets to the file name within the pages directory. For example, a file named [username].tsx will match any route like /users/johndoe or /users/janesmith, where johndoe and janesmith are dynamic parameters.
Here's how you might define a dynamic route:
1// pages/users/[username].tsx 2import { useRouter } from 'next/router'; 3 4const UserPage = () => { 5 const router = useRouter(); 6 const { username } = router.query; 7 8 return <div>Welcome, {username}!</div>; 9}; 10 11export default UserPage;
When dealing with dynamic routes, you'll often need to access route parameters and query strings to tailor the page content to the user's request. The useRouter hook from Next.js provides access to the router object, which contains the query object where you can find all the route parameters and query string values.
While the link component is great for declarative navigation, there are times when you need to navigate between routes programmatically. This is where the useRouter hook shines, offering methods like push and replace to change routes in response to user actions or other events.
For instance, you might want to redirect a user to the home page after a successful form submission:
1import { useRouter } from 'next/router'; 2 3const FormComponent = () => { 4 const router = useRouter(); 5 6 const handleSubmit = async (event) => { 7 event.preventDefault(); 8 // Form processing logic here 9 // ... 10 // Redirect to the home page 11 router.push('/'); 12 }; 13 14 return ( 15 <form onSubmit={handleSubmit}> 16 {/* form fields */} 17 <button type="submit">Submit</button> 18 </form> 19 ); 20}; 21 22export default FormComponent;
In this example, we use the push method from the useRouter hook to navigate to the home page after the form is submitted.
While Next.js provides a powerful and efficient routing system out of the box, there are scenarios where you might need more control over the server-side behavior of your routes. This is where a custom server comes into play, allowing you to extend the capabilities of your Next.js app beyond the constraints of the file system-based router.
A custom server in a Next.js app is typically used when you need to handle routes that are not supported by the file system-based router or when you require custom server-side functionalities such as:
Server-side route handling for complex patterns or redirects
Integrating with a custom authentication system
Handling webhooks or API endpoints within the same server
Applying custom middleware for logging, security headers, or other server-side processes
To set up a custom server, you can use a Node.js server framework like Express.js. This involves creating a server file where you define your server and middleware, and then telling Next.js to use this server when starting the application.
Here's a basic setup using Express.js:
1const express = require('express'); 2const next = require('next'); 3 4const dev = process.env.NODE_ENV !== 'production'; 5const app = next({ dev }); 6const handle = app.getRequestHandler(); 7 8app.prepare().then(() => { 9 const server = express(); 10 11 // Define custom server routes here 12 13 // Fallback to Next.js page handler 14 server.get('*', (req, res) => { 15 return handle(req, res); 16 }); 17 18 server.listen(3000, (err) => { 19 if (err) throw err; 20 console.log('> Ready on http://localhost:3000'); 21 }); 22});
With your custom server in place, you can start mapping routes to your Next.js pages. This involves defining route patterns and associating them with specific page components.
For example, to map a custom route to a Next.js page:
1server.get('/p/:id', (req, res) => { 2 const actualPage = '/post'; 3 const queryParams = { id: req.params.id }; 4 app.render(req, res, actualPage, queryParams); 5});
In this snippet, requests to /p/:id are mapped to the /post page, with the dynamic parameter passed as a query.
Your custom server is also the perfect place to implement server-side logic and middleware. This could include user authentication, request logging, or any other server-side functionality that needs to run before your pages are served.
For example, to add a simple logging middleware:
1server.use((req, res, next) => { 2 console.log(`Received request for ${req.url}`); 3 next(); 4});
The App component in Next.js plays a crucial role in the overall architecture of a Next.js application. It serves as the main entry point for all pages, and by default, it's a built-in component that Next.js provides to initialize pages. However, developers can override this default App component with a custom one, which opens up many possibilities for enhancing the application.
The App component is the wrapper around your page components. It is executed on both the server and the client, making it the perfect place to insert global logic that needs to run for every page visit. This includes setting up page layouts, page transitions, theme providers, global CSS, and any other global JavaScript that should be executed server-side and client-side.
Overriding the default App component with a custom one provides several benefits that can significantly improve the developer experience and the end-user interface:
Persistent Layouts: By using a custom App component, you can maintain the same layout across different pages without having to duplicate code. This is particularly useful for elements like headers, footers, and navigation bars that should remain consistent as users navigate through your app.
Global State Management: It's an ideal place to insert providers for state management libraries (like Redux or Context API), ensuring that state is accessible by any page component in the application.
Global Styles: A custom App component is the only place in a Next.js app where you can import global CSS files. This ensures that your styling is consistent across all pages and is applied correctly with server-side rendering.
Performance Optimizations: You can implement performance optimizations such as code splitting, caching, and prefetching at the App level, benefiting the entire application.
Custom Error Handling: The custom App component allows you to catch and handle errors using React error boundaries, providing a better user experience when something goes wrong.
Enhanced Data Handling: You can inject additional data into pages using the pageProps object, which can be useful for fetching data that should be available on every page, such as user authentication status or global settings.
Next.js offers a special file that allows you to initialize all your pages: the custom _app.tsx file. This file is used to override the default App component provided by Next.js, enabling you to add global styles, layout components, and additional page properties across your entire application.
Creating a custom App component in Next.js is straightforward. Here's how you can set up your own _app.tsx file to enhance your Next.js app:
Create the app.tsx File: In your project's ‘pages' directory, create a new file named* app.tsx. This is where you will define your custom App component.
Import AppProps: At the top of your _app.tsx file, import the AppProps type from next/app. This type will be used to type your App component's props, ensuring you have access to the necessary properties like Component and pageProps.
Define the Custom App Component: Create a functional component that takes AppProps as its argument. This component will receive two important properties: Component, which is the active page, and pageProps, which are the props preloaded for your page by Next.js data fetching methods.
Return Your Page Layout: Within the component, return your desired layout structure, including any global components like headers or footers. Make sure to render the Component prop with the spread pageProps.
Here's an example of what your _app.tsx file might look like:
1import type { AppProps } from 'next/app'; 2 3const MyApp = ({ Component, pageProps }: AppProps) => { 4 return ( 5 <> 6 {/* Global layout components go here (e.g., header, footer) */} 7 <Component {...pageProps} /> 8 </> 9 ); 10}; 11 12export default MyApp;
Add Global CSS (Optional): If you want to include global CSS, you can import it at the top of your _app.tsx file. This is the only place where you should import global CSS files in Next.js.
Restart Your Development Server: If your Next.js app was running while you created the _app.tsx file, restart your development server to ensure your custom App component is recognized.
The AppProps type is essential for typing the props of your custom App component. It provides you with a contract that guarantees the structure of the props passed to your App component, which includes:
Component: The active page being rendered. This prop changes when you navigate between routes.
pageProps: An object containing the initial props that were preloaded for your page by Next.js data fetching methods, or an empty object if there were none.
By using AppProps, you ensure that your custom App component is receiving the correct props from Next.js, which allows you to handle page initialization and global configurations with confidence.
One of the most common uses for a custom _app.tsx file in Next.js is to implement a shared layout that persists across page changes. This shared layout typically includes elements like headers, footers, and sidebars that you want to display on every page of your app.
To create a shared layout, you'll need to define the layout components that should be present on every page and then include them in your custom App component. Here's how to do it:
Define Your Layout Components: Create the components that will make up your shared layout. For example, you might have a Header component and a Footer component.
Import Layout Components in app.tsx: In your app.tsx file, import the layout components you've created.
Wrap the Component Prop with Your Layout: Inside the render method of your custom App component, wrap the Component prop with your layout components. This ensures that every page will be rendered within the shared layout.
Here's an example of how you might implement a shared layout with a common header and footer in your custom App component:
1// components/Header.tsx 2const Header = () => { 3 return ( 4 <header> 5 {/* Header content */} 6 </header> 7 ); 8};
1// components/Footer.tsx 2const Footer = () => { 3 return ( 4 <footer> 5 {/* Footer content */} 6 </footer> 7 ); 8};
1// pages/_app.tsx 2import type { AppProps } from 'next/app'; 3import Header from '../components/Header'; 4import Footer from '../components/Footer'; 5 6const MyApp = ({ Component, pageProps }: AppProps) => { 7 return ( 8 <> 9 <Header /> 10 <main> 11 <Component {...pageProps} /> 12 </main> 13 <Footer /> 14 </> 15 ); 16}; 17 18export default MyApp;
In this code, the Header and Footer components are imported and used to wrap the Component prop, which represents the current page. The main tag is used to wrap the Component prop, providing a semantic HTML structure and potentially allowing for main content-specific styling.
In Next.js, each page can receive additional data that is not part of the URL parameters. This data is passed through an object called pageProps. Understanding and utilizing pageProps is crucial for customizing the content and behavior of your pages based on external data sources.
The pageProps object is a key feature of the custom App component in Next.js. It contains the initial props that your page components can receive, which are preloaded through Next.js's data-fetching methods like getStaticProps or getServerSideProps. When you navigate between pages, Next.js automatically populates pageProps with the correct data for each page.
To pass additional data to your pages, you can use Next.js's data fetching methods within your page components. However, there might be scenarios where you want to fetch and inject data into all pages or multiple pages at once. This is where the custom App component's ability to enhance pageProps comes into play.
Here's how you can fetch and pass additional data to pages:
Fetch Data in the Custom App Component: If you need to fetch data that should be available on every page, such as user authentication status or site-wide settings, you can do so in the custom App component.
Enhance pageProps with Additional Data: After fetching the data, you can add it to the pageProps object, which will then be passed to each page component.
Here's an example of how you might fetch additional data and pass it to pages through pageProps:
1// pages/_app.tsx 2import type { AppProps } from 'next/app'; 3 4const MyApp = ({ Component, pageProps }: AppProps) => { 5 // Fetch additional data here (e.g., user authentication status) 6 const additionalData = { isAuthenticated: true }; // Example data 7 8 // Enhance pageProps with the additional data 9 const enhancedPageProps = { ...pageProps, ...additionalData }; 10 11 return ( 12 <Component {...enhancedPageProps} /> 13 ); 14}; 15 16export default MyApp;
In this code snippet, we simulate fetching additional data by creating an additionalData object with an isAuthenticated property. We then spread both pageProps and additionalData into a new enhancedPageProps object, which is passed to the Component prop representing the current page.
Throughout this exploration of Next.js's custom routes and the custom App component, we've uncovered the power and flexibility that Next.js offers to developers. From the simplicity of the file system-based router to the dynamic capabilities of custom servers, Next.js stands out as a framework designed to streamline the development process while offering the tools needed to build sophisticated, user-friendly web applications.
We delved into the intricacies of the file system-based router, learning how to create dynamic routes and handle route parameters with ease. We also discovered the benefits of extending routing capabilities with a custom server, allowing for complex server-side logic and middleware integration.
The custom App component emerged as a central piece of the Next.js ecosystem, enabling us to implement shared layouts, inject global CSS, and pass additional data to pages with the pageProps object. By mastering these concepts, you can craft a cohesive and consistent user experience across your entire application.
Tired of manually designing screens, coding on weekends, and technical debt? Let DhiWise handle it for you!
You can build an e-commerce store, healthcare app, portfolio, blogging website, social media or admin panel right away. Use our library of 40+ pre-built free templates to create your first application using DhiWise.