Working with Pages & Layouts in Next.js 14

Table Of content :

In the ever-evolving world of web development, building modern and performant applications has become a top priority. Next.js, a React framework created by Vercel, has gained immense popularity for its ability to simplify the process of creating server-rendered React applications. With the release of Next.js 14, developers have been introduced to exciting new features and improvements, among which are the enhanced capabilities for working with pages and layouts.

Pages in Next.js 14

At the core of Next.js lies its file-based routing system, where each file in the pages directory corresponds to a route in your application. This intuitive approach makes it easy to create and manage pages, as well as to reason about the structure of your application.

Creating a New Page

To create a new page in Next.js 14, simply add a new file (e.g., about.js) to the pages directory. By convention, the file name corresponds to the route path (/about in this case). Here's an example of a basic page component:

// pages/about.js
import React from 'react';

const AboutPage = () => {
  return (
    <div>
      <h1>About Us</h1>
      <p>Welcome to our awesome website!</p>
    </div>
  );
};

export default AboutPage;

Dynamic Pages and Routing

Next.js 14 also supports dynamic pages and routing, which allows you to create pages that can handle dynamic data. For example, you might want to create a page that displays information about a specific product based on its ID. To achieve this, you can use dynamic imports and dynamic routing.

// pages/products/[id].js
import React from 'react';
import { useRouter } from 'next/router';

const ProductPage = () => {
  const router = useRouter();
  const { id } = router.query;

  return (
    <div>
      <h1>Product: {id}</h1>
      {/* Fetch and display product details */}
    </div>
  );
};

export default ProductPage;

In this example, the [id].js file in the pages/products directory represents a dynamic page. The useRouter hook from Next.js provides access to the current route parameters, allowing you to fetch and display data specific to the requested product ID.

Layouts in Next.js 14

Layouts in Next.js 14 provide a way to share common UI elements across multiple pages, such as headers, footers, and sidebars. This helps maintain consistency throughout your application and reduces code duplication.

Creating a Layout Component

To create a layout component, you can define a new React component that wraps the content of your pages. Here's an example of a basic layout component:

// components/Layout.js
import React from 'react';
import Header from './Header';
import Footer from './Footer';

const Layout = ({ children }) => {
  return (
    <div>
      <Header />
      <main>{children}</main>
      <Footer />
    </div>
  );
};

export default Layout;

In this example, the Layout component renders a Header, the page content (passed as the children prop), and a Footer. You can further customize the layout by adding additional components or styling as needed.

Applying a Layout to a Page

To apply a layout to a page, you can import and render the layout component within your page component. Here's an example:

// pages/index.js
import React from 'react';
import Layout from '../components/Layout';

const HomePage = () => {
  return (
    <Layout>
      <h1>Welcome to our website!</h1>
      {/* Page content */}
    </Layout>
  );
};

export default HomePage;

In this example, the HomePage component is wrapped by the Layout component, ensuring that the header and footer are rendered along with the page content.

Nested Layouts

Next.js 14 also supports nested layouts, which can be useful when you have different layout requirements for different sections of your application. For example, you might have a main layout for your website, and a separate layout for your blog section.

To create nested layouts, you can simply render one layout component inside another. Here's an example:

// components/MainLayout.js
import React from 'react';
import Header from './Header';
import Footer from './Footer';

const MainLayout = ({ children }) => {
  return (
    <div>
      <Header />
      <main>{children}</main>
      <Footer />
    </div>
  );
};

export default MainLayout;

// components/BlogLayout.js
import React from 'react';
import MainLayout from './MainLayout';
import Sidebar from './Sidebar';

const BlogLayout = ({ children }) => {
  return (
    <MainLayout>
      <div style={{ display: 'flex' }}>
        <Sidebar />
        <div style={{ flex: 1 }}>{children}</div>
      </div>
    </MainLayout>
  );
};

export default BlogLayout;

In this example, the BlogLayout component renders the MainLayout component, which includes the header and footer. Additionally, the BlogLayout component adds a sidebar and a container for the page content.

You can then apply the BlogLayout to your blog pages:

// pages/blog/[slug].js
import React from 'react';
import BlogLayout from '../../components/BlogLayout';

const BlogPostPage = () => {
  return (
    <BlogLayout>
      {/* Blog post content */}
    </BlogLayout>
  );
};

export default BlogPostPage;

Server Components

Next.js 14 introduces a new type of React component called Server Components. These components are designed to be rendered on the server and streamed to the client, providing improved performance and a more efficient rendering process.

Creating a Server Component

To create a Server Component, you can use the 'use server' directive at the top of your component file. Here's an example:

// 'use server' directive
import React from 'react';

const ServerComponent = () => {
  return (
    <div>
      <h1>This is a Server Component</h1>
      {/* Component content */}
    </div>
  );
};

export default ServerComponent;

Server Components can be used in both pages and layouts, allowing you to optimize the rendering process and improve the overall performance of your application.

Data Fetching

Next.js 14 provides several options for fetching data in your application, including client-side fetching, server-side fetching, and static data fetching.

Client-side Data Fetching

Client-side data fetching is suitable for situations where the data needs to be loaded dynamically after the initial page render. You can use React's built-in useEffect hook or a third-party library like react-query or swr to fetch data on the client-side.

import React, { useState, useEffect } from 'react';

const ClientDataFetching = () => {
  const [data, setData] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      const response = await fetch('/api/data');
      const data = await response.json();
      setData(data);
    };

    fetchData();
  }, []);

  return (
    <div>
      {data ? (
        <div>
          {/* Render data */}
        </div>
      ) : (
        <div>Loading...</div>
      )}
    </div>
  );
};

export default ClientDataFetching;

Server-side Data Fetching

Server-side data fetching is useful when you need to fetch data before rendering the page on the server. Next.js provides the getServerSideProps function, which allows you to fetch data on the server and pass it as props to your page component.

// pages/products.js
import React from 'react';

const ProductsPage = ({ products }) => {
  return (
    <div>
      <h1>Products</h1>
      <ul>
        {products.map((product) => (
          <li key={product.id}>{product.name}</li>
        ))}
      </ul>
    </div>
  );
};

export const getServerSideProps = async () => {
  const response = await fetch('https://api.example.com/products');
  const products = await response.json();

  return {
    props: {
      products,
    },
  };
};

export default ProductsPage;

In this example, the getServerSideProps function fetches data from an API and passes it as props to the ProductsPage component, which then renders the products.

Static Data Fetching

Static data fetching is useful for pages that can be pre-rendered at build time. Next.js provides the getStaticProps and getStaticPaths functions for this purpose.

// pages/blog/[slug].js
import React from 'react';
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';

const BlogPostPage = ({ frontmatter, content }) => {
  return (
    <div>
      <h1>{frontmatter.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: content }} />
    </div>
  );
};

export const getStaticPaths = async () => {
  const files = fs.readdirSync(path.join('posts'));

  const paths = files.map((filename) => ({
    params: {
      slug: filename.replace('.md', ''),
    },
  }));

  return {
    paths,
    fallback: false,
  };
};

export const getStaticProps = async ({ params: { slug } }) => {
  const markdownWithMeta = fs.readFileSync(path.join('posts', slug + '.md'), 'utf-8');
  const { data: frontmatter, content } = matter(markdownWithMeta);

  return {
    props: {
      frontmatter,
      content,
    },
  };
};

export default BlogPostPage;

In this example, the getStaticPaths function generates the list of paths (slugs) for the blog posts. The getStaticProps function fetches the content and metadata for a specific blog post based on the slug and passes it as props to the BlogPostPage component.

Incremental Static Regeneration (ISR)

Next.js 14 introduces Incremental Static Regeneration (ISR), which allows you to update static pages after they have been built, providing a balance between the benefits of static and dynamic rendering.

To enable ISR, you can use the revalidate option in getStaticProps:

// pages/products/[id].js
export const getStaticProps = async ({ params }) => {
  const response = await fetch(`https://api.example.com/products/${params.id}`);
  const product = await response.json();

  return {
    props: {
      product,
    },
    revalidate: 60, // Regenerate the page every 60 seconds
  };
};

In this example, the revalidate option specifies that the page should be regenerated every 60 seconds, ensuring that the data remains up-to-date.

Styling and CSS

Next.js 14 provides several options for styling your application, including CSS Modules, global styles, and importing styles from third-party libraries.

CSS Modules

CSS Modules is a popular approach to scoping CSS styles in Next.js. Each component can have its own CSS file, and the styles are automatically scoped to that component, preventing naming conflicts.

// components/Button.js
import React from 'react';
import styles from './Button.module.css';

const Button = ({ children }) => {
  return <button className={styles.button}>{children}</button>;
};

export default Button;

// components/Button.module.css
.button {
  background-color: blue;
  color: white;
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

In this example, the Button component imports its styles from the Button.module.css file, and the styles are scoped to that component using the styles object.

Global Styles

If you need to apply styles globally across your application, you can create a global CSS file and import it into your application.

// pages/_app.js
import '../styles/globals.css';

function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />;
}

export default MyApp;

// styles/globals.css
body {
  font-family: Arial, sans-serif;
  margin: 0;
  padding: 0;
}

In this example, the globals.css file contains global styles that are applied to the entire application by importing it in the _app.js file.

Importing Styles from a Third-Party Library

Next.js also allows you to import styles from third-party libraries, such as Bootstrap or Material-UI.

// pages/_app.js
import 'bootstrap/dist/css/bootstrap.min.css';

function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />;
}

export default MyApp;

// components/NavBar.js
import React from 'react';

const NavBar = () => {
  return (
    <nav className="navbar navbar-expand-lg navbar-dark bg-primary">
      {/* Navbar content */}
    </nav>
  );
};

export default NavBar;

In this example, the Bootstrap CSS file is imported in the _app.js file, allowing you to use Bootstrap classes throughout your application, as demonstrated in the NavBar component.

Deployment and Performance

After building your Next.js application, you can deploy it to various hosting platforms, such as Vercel, Netlify, or a custom server.

Building and Deploying a Next.js Application

To build your Next.js application for production, run the following command:

Copy codenpm run build

This command will create an optimized production build in the .next directory.

Next, you can start the production server by running:

Copy codenpm start

This will serve your application from the .next directory, making it ready for deployment.

Performance Optimizations

Next.js 14 comes with several built-in performance optimizations, including code splitting, image optimization, and more.

Code Splitting

Next.js automatically splits your application code into smaller chunks, allowing for lazy loading of non-critical code and improving the initial load time.

Image Optimization

Next.js provides an optimized Image component that automatically optimizes images for performance by resizing, compressing, and serving them in modern formats like WebP.

import Image from 'next/image';

const ImageExample = () => {
  return (
    <Image
      src="/images/example.jpg"
      alt="Example Image"
      width={500}
      height={300}
    />
  );
};

In this example, the Image component will automatically optimize the example.jpg image for performance.

Conclusion

Working with pages and layouts in Next.js 14 has become more powerful and flexible than ever before. With the introduction of Server Components, improved data fetching options, and enhanced performance optimizations, developers can build modern web applications that are fast, efficient, and user-friendly.

As the Next.js ecosystem continues to grow, we can expect even more exciting features and improvements in the future. Whether you're building a simple website or a complex web application, Next.js 14 provides a solid foundation for creating high-performance, server-rendered React applications.

For further learning and staying up-to-date with the latest Next.js developments, you can refer to the official Next.js documentation, join the Next.js community, and explore various online resources and tutorials.