Blog Post

Apps on Azure Blog
6 MIN READ

Build full-stack Next.js apps with Azure Static Web Apps

thomasgauvinmsft's avatar
Mar 21, 2024

Next.js is the most popular hybrid rendering frontend framework and is the most popular React framework as well. With Azure Static Web Apps’ recently announced improved support for Next.js, we can easily deploy and host our Next.js applications on Azure, while leveraging Next.js’ recent features such as React Server Components and Server Actions. In this article, we’ll build and deploy a Next.js application to Azure Static Web Apps (and you can follow along with the free plan!). (Follow along with the complete application available on GitHub, or try it out here).

Architecture of Next.js full-stack hosting on Azure Static Web Apps

 

How Next.js on Azure Static Web Apps works

 

When hosting a Next.js site on Azure Static Web Apps, Static Web Apps handles routing to properly serve your Next.js application. Requests for static content will be routed to Static Web Apps’ globally distributed content host, while dynamic requests (React Server Components, server-side rendered requests, etc.) will be proxied to the managed App Service plan to be resolved by the Next.js server.

 

This hosting by Azure Static Web Apps is unique to Next.js web applications to provide full feature compatibility with Next.js. If you want to increase the compute dedicated to hosting your Next.js site, we plan to allow you to attach your own App Service instance to be managed by Azure Static Web Apps to retain convenient deployments.

 

Prerequisites

As with other tutorials in this series of full-stack frameworks on Azure Static Web Apps, this tutorial requires that you have Node.js installed locally, an Azure subscription and a GitHub account. For this sample application, we’ll be using an existing Azure CosmosDB for NoSQL to demonstrate how we can access sensitive backend services such as databases or 3rd party APIs from Next.js’ own server functions. The schema of the Azure CosmosDB for NoSQL is as follows:

SWAStore (Database)
	Items (Container)
		Item (Item)
			{	
				id: string
				title: string
				price: number
			}
	Sales (Container)
		Item (Item)
			{
				id: string
				date: string (date in ISO String format)
				items : [
					{
						id: string
						quantity: number
					}
				]
			}

 

Get Started

 

For this article, we’ll be building a sample e-commerce back store management application featuring CRUD access with Next.js deployed to Static Web Apps. To start, we’ll create a new Next.js project, which will both query and mutate the data from our CosmosDB database, and show the UI for the CRUD interactions.

 

npx create-next-app@latest nextjs-swa-full-stack-app
cd nextjs-swa-full-stack-app
npm install /cosmos
code .
npm run dev

 

We now have our development server running at http://localhost:3000 which we can access locally. We’ll also set our environment variables to access our sensitive backend services, in this case our CosmosDB database. Create a .env.local file within the root of our project and add the CosmosDB credentials:

 

COSMOSDB_KEY="<ENTER COSMOSDB KEY HERE>"
COSMOSDB_ENDPOINT="<ENTER COSMOSDB URL HERE>"

 

 

Add the Items page with to the Next.js project

 

To showcase the full-stack capabilities of Next.js, we’ll create a simple Items page that displays the items from our database and allows CRUD access to items from our database. To do so, we can create a new file /app/items/page.js, and add the following contents:

 

import { CosmosClient } from "@azure/cosmos";
import Link from "next/link";
import { revalidatePath } from "next/cache";

export const dynamic = 'force-dynamic';

export default async function Page() {
    const client = new CosmosClient({ endpoint: process.env["COSMOSDB_ENDPOINT"], key: process.env["COSMOSDB_KEY"] });
    const database = client.database("SWAStore");
    const container = database.container("Items");

    const { resources: items } = await container.items.readAll().fetchAll();

    async function deleteItem(id) {
        "use server";      
         try{
             await container.item(id, id).delete();
             revalidatePath("/items");
         }
         catch(err){
             return {
                 message: 'Failed to delete item'
             }
         }        
    }

    return (
        <>
            <div>
                <div>
                    {items.map((item) => (
                        <div key={item.id}>
                            <div>#{item.id} - {item.title}</div>
                            <div>${item.price}</div>
                            <div>
                                <Link
                                    href={`/items/edit/${item.id}`}
                                >
                                    Edit
                                </Link>
                                <form action={deleteItem.bind(null, item.id)}>
                                    <button>
                                        Delete
                                    </button>
                                </form>
                            </div>
                        </div>
                    ))}
                </div>
                <div>
                    <Link
                        href="/items/create"
                    >
                        Create New Item
                    </Link>
                </div>
            </div>
        </>
    );
}

 

This code snippet is our component for the Items page. As you may notice, this component contains not only the display logic for our items page, but also the functions that retrieve and edit the content from the database, in this case fetching all items and deleting an item by id. This is using Next.js’ support for React Server Components and Server Actions, which allow us to perform server-side actions within our component file. This is because this component is executed solely on the server, with access sensitive credentials such as our database passwords.

 

React Server Components and Server Actions provide us with a great developer experience because we have server-side functionality within the component and it's rendering logic within a single file, just as has been done in the past with PHP and other server templating languages! However, Next.js provides a much smoother experience, allowing us to have SPA navigations on page change and isomorphic JavaScript (executes on the server and client!)

 

We can iterate on this application to add the ability to create new items and edit existing items, which follows a similar implementation to the one detailed above for the delete. The implementations of the create and edit functionality, as well as the complete application, are available in the source code that accompanies this blog post. 

 

Screenshot of the Items page with CRUD functionality

 

Digging into server-side rendering and caching in Next.js

 

By default, all Next.js pages are server-side rendered. The rendering mode can be configured for client components to opt out of server-side rendering. With Next.js server-side rendering, new page hits will receive the server-side rendered HTML with values populated. However, new page navigations within the Next.js site will call Next.js’ specific endpoints for executing React Server Components (RSCs) on the server and returning the server-computed RSCs with the RSC wire format as can be inspected from the network responses. 

 

This is an implementation detail of Next.js and React Server Components, which you don’t need to worry about as it is handled by Next.js itself. I think it’s interesting to call out how Next.js is able to provide a server-side rendering while provide fluid SPA-like interaction from page to page.

 

Digging into Next.js' RSC responses

 

On the topic of caching, Next.js heavily caches the requests to external services such as APIs or databases. As you can see, in our code snippet, we added `export const dynamic = 'force-dynamic';` in order to prevent these pages from being rendered at build time. These pages can be rendered at build time, and to do so, the environment variables required by the CosmosDB client would need to be included in build environment. Check out the Azure Static Web Apps docs for more details.

 

 

Deploy Next.js to Azure Static Web Apps

 

Before deploying our Next.js project to Azure Static Web Apps, we first need to configure our package.json to indicate that this project requires Node 18.17.1 to build. This is currently required because Static Web Apps defaults to Node 16 to build projects, though this will no longer be required soon as we are working to update the default Node version.

 

We’re now ready to deploy our Next.js project to Static Web Apps! Start by creating a GitHub repository for the application (it’s the easiest way to deploy to Static Web Apps).

 

With our newly created GitHub repository, we can create a new Azure Static Web Apps resource from the Azure Portal. In the deployment details, we must select our GitHub repository and specify the Next.js build preset. This will configure our GitHub Actions deployment with the proper configuration.

 

Finally, since we’re using environment variables to safely store our database credentials out of our source code, we’ll navigate to the Static Web Apps > Environment variables tab. Here, we can se the COSMOSDB_KEY and COSMOSDB_ENDPOINT environment variables.

 

Tada! Our app is successfully deployed to Static Web Apps: https://agreeable-field-07aa9c60f.5.azurestaticapps.net/items 

 

Conclusion

 

In this article, we detailed how we can build a full-stack, server-side rendered Next.js application and host it on Azure Static Web Apps. This demo application features recent Next.js features such as React Server Components and Server Actions, but you can also build and deploy Next.js projects using API routes and the page router to Azure Static Web Apps. And with the recently improved support for Next.js on Azure Static Web Apps, you can make use of the built-in authentication to add authorization to your full-stack application.

 

Get started deploying full-stack web apps for free on Azure with Azure Static Web Apps!

Updated Mar 21, 2024
Version 2.0
No CommentsBe the first to comment