Next.js 14: All the Innovations Making Deployment Simple and Efficient
Unlike previous versions that focused on adding new APIs, Next.js 14 distinguishes itself by its dedication to refining existing tools, providing developers with a more efficient and intuitive solution for building modern web projects.
In this article, we’ll explore all the significant features of this new version of the full-stack framework built on React, examining the motivations behind their introduction and how we can implement them in our applications.
Turbopack: A Turbo Compiler for Next.js
One of the most significant additions in Next.js 14 is the introduction of Turbopack, an innovative compiler that replaces Webpack. Built on the Rust programming language, this new system promises to revolutionize the development process with local server startup times 53.3% faster and code updates 94.7% faster, thanks to Fast Refresh. Turbopack is a substantial leap forward in local development performance, providing tangible speed and reliability benefits.
To test Turbopack immediately:
Ensure you have version 14 of Next.js in your project. You can update it by modifying the dependency in your package.json file and then running npm install or yarn install.
In some versions of Next.js 14, Turbopack might not be enabled by default. If necessary, you can enable it by adding a flag to your start command. For example, you could modify your dev script in package.json from “dev”: “next dev” to “dev”: “next dev –turbo”.
Server Actions: Simplifying Data Mutations
Next.js 14 introduces Server Actions as a stable feature, simplifying web application development significantly. Server Actions allow you to define functions executed on the server that can be invoked directly by React components on the client side. This approach makes data mutations easier, enhancing the user experience in scenarios with slow connections or less powerful devices. Developers can now implement server-side features more intuitively, reducing code complexity and improving efficiency.
Server Actions are deeply integrated into the entire App Router model. You can:
- Revalidate cached data with revalidatePath() or revalidateTag().
- Redirect to different routes using redirect().
- Set and read cookies through cookies().
- Handle optimistic UI updates with useOptimistic().
- Capture and display server errors with useFormState().
- Show loading states on the client with useFormStatus().
Here’s an example:
// app/page.tsx
export default function Page() {
async function createItem(formData) {
'use server';
// Logic to create an item (e.g., save to the database)
return { success: true };
}
// Rest of the component…
}
In the same component, you can now use this function directly to perform server-side actions, such as responding to a form submission:
// Continued in app/page.tsx
export default function Page() {
async function handleSubmit(event) {
event.preventDefault();
const formData = new FormData(event.currentTarget);
const result = await createItem(formData);
if (result.success) {
// Handle the success of the action
}
}
return (
{/* Form elements */} Submit
);
}
Here, the createItem
function is executed on the server when the form is submitted. This approach allows handling server-side operations such as data saving or other backend processing efficiently and securely, directly within React components.
Here’s an example of how you might structure a Next.js 14 component containing two forms, each interacting with different Server Actions using GET and POST methods:
Defining Server Actions in External Files:
// actions/getData.js
export async function getData() {
'use server';
// Logic to retrieve data (GET)
}
// actions/postData.js
export async function postData(formData) {
'use server';
// Logic to send data (POST)
}
Creating the Component with Two Forms:
// app/page.tsx
import React from 'react';
import { getData } from '../actions/getData';
import { postData } from '../actions/postData';
export default function Page() {
async function handleGetSubmit(event) {
event.preventDefault();
const result = await getData();
// Handle the response
}
async function handlePostSubmit(event) {
event.preventDefault();
const formData = new FormData(event.currentTarget);
const result = await postData(formData);
// Handle the response
}
return (
<div>
<form onSubmit={handleGetSubmit}>
{/* Form elements for GET */}
<button type="submit">Get Data</button>
</form>
<form onSubmit={handlePostSubmit}>
{/* Form elements for POST */}
<button type="submit">Post Data</button>
</form>
</div>
);
}
Partial Prerendering: Combining Static and Dynamic
Another experimental feature in Next.js 14 is Partial Prerendering. This function aims to combine the benefits of static site generation (SSG) and server-side rendering (SSR). Using React Suspense component boundaries, Partial Prerendering determines which parts of the application are static and which are dynamic. Static parts are prepared as HTML, while dynamic parts are updated only when necessary. This hybrid approach promises to simplify development by offering the performance of staticity with the flexibility of dynamic content.
To implement Partial Prerendering in Next.js 14, you will use React Suspense features to define which parts of your application can be statically prerendered and which need to be loaded dynamically. Here’s an example:
Use Suspense to define the dynamic parts of your component that should be loaded after the static shell is loaded:
// app/page.tsx
import React, { Suspense } from 'react';
const DynamicComponent = React.lazy(() => import('./DynamicComponent'));
export default function Page() {
return (
<div>
<h1>Static Title</h1>
<Suspense fallback={<p>Loading...</p>}>
<DynamicComponent />
</Suspense>
</div>
);
}
Define the component that will be loaded dynamically:
// DynamicComponent.tsx
export default function DynamicComponent() {
// Logic for the dynamic component
return <div>Dynamic Content</div>;
}
DynamicComponent
will be loaded dynamically, while the rest of the page (<h1>Static Title</h1>
) will be statically prerendered. This approach combines the advantages of static page generation with the flexibility of dynamic rendering, improving performance and the user experience.
Improvements to Metadata: Optimizing User Experience
Next.js 14 has introduced significant improvements in metadata management. This version separates blocking metadata from non-blocking metadata, ensuring that the initial page view is not slowed down by non-essential metadata. Optimized metadata management is crucial for a smooth user experience, avoiding issues such as page flickering or changes in element layout due to viewport or theme variations.
Before your page’s content can be transmitted by the server, some crucial metadata related to the viewport, color scheme, and theme needs to be sent to the browser first. Ensuring that these meta tags are sent with the initial page content helps ensure a smooth user experience, preventing flickering due to theme color changes or layout shifts due to viewport changes.
In Next.js 14, there is now a separation between blocking and non-blocking metadata. Only a small subset of metadata options is blocking, ensuring that non-blocking metadata does not prevent a partially prerendered page from serving the static shell.
The following metadata options are now deprecated and will be removed from metadata in a future major version:
- viewport: Sets the initial zoom and other viewport properties.
- colorScheme: Sets supported modes (light/dark) for the viewport.
- themeColor: Sets the color of the border around the viewport.
Starting from Next.js 14, there are new viewport
and generateViewport
options to replace these options. All other metadata options remain unchanged. You can start adopting these new APIs today, and the existing metadata options will continue to work.