Build a full-stack, server-rendered Nuxt site with Azure Static Web Apps
Published Mar 20 2024 12:22 PM 1,985 Views

Nuxt is a powerful frontend JavaScript framework for Vue.js, with full-stack and server-side capabilities such as server-side rendering and server routes. Since Nuxt 3, we can deploy our full-stack Nuxt applications to Azure Static Web Apps with zero-configuration required! Let’s jump in to see how we can build and deploy a full-stack Nuxt app on Azure with Azure Static Web Apps. (Access the complete application on GitHub!, and the deployed application at https://happy-beach-029f0310f.5.azurestaticapps.net/items)

 

Architecture diagram Nuxt app deployed to Azure Static Web AppsArchitecture diagram Nuxt app deployed to Azure Static Web Apps

 

When we deploy our Nuxt site to Azure Static Web Apps, Nuxt will detect our deployment target and automatically build our Nuxt project into 2 separate bundles: a bundle of static files, which will be deployed to Azure Static Web Apps’ globally distributed static host, and a bundle of Azure Functions files, which will be deployed to Azure Static Web Apps’ managed functions. This makes hosting Nuxt on Static Web Apps for optimized frontend hosting a breeze! Alternatively, we can specify the Azure preset in our build command.  

 

Illustration of Nuxt build step into split static and Azure Functions bundlesIllustration of Nuxt build step into split static and Azure Functions bundles

 

Prerequisites

 

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 Nuxt’s own server-routes. 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
					}
				]
			}

 

 

Getting started

 

We’ll get started by creating a Nuxt project. This project will contain the pages for our website with server-side rendering along with server routes responsible for fetching data from our database. This will be a sample dashboard application that allows us to managed sales for an e-commerce site, specifying items in our store and sales that occurred.

 

npx nuxi@latest init nuxt-swa-full-stack-app
cd nuxt-swa-full-stack-app
npm install /cosmos
code .
npm run dev

 

These commands will scaffold a Nuxt project, install Node dependencies with npm, open the project in Visual Studio Code and run the Nuxt development server. We can now access our project at http://localhost:3000. We’ll follow up by setting our environment variables with our CosmosDB credentials so that we can access our database from our application. Within nuxt-swa-full-stack-app, run the following:

 

echo 'COSMOSDB_KEY="<ENTER COSMOSDB KEY HERE>"
COSMOSDB_ENDPOINT="<ENTER COSMOSDB URL HERE>"' > .env

 

This will create a .env file which will be read by Nuxt and make these environment variables available within our Nuxt project, as indicated in Nuxt docs. We’ll make use of these environment variables in the nuxt.config.ts file, setting these environment variables as configuration that will be accessible in our project.

 

export default defineNuxtConfig({
  devtools: { enabled: true },
  runtimeConfig: {
    // Will be available at runtime
    cosmosdbKey: process.env.COSMOSDB_KEY,
    cosmosdbEndpoint: process.env.COSMOSDB_ENDPOINT
  }
})

 

 

Create the server API routes

 

We’ll start by creating the server routes which will access our Azure CosmosDB database and provide an API that can be accessed from our Nuxt pages. Create the file /server/api/Items/index.js which will contain our server route code, and add the logic to handle the request.

 

import { CosmosClient } from "@azure/cosmos";
export default defineEventHandler(async (event) => {
    const config = useRuntimeConfig(event)

    const client = new CosmosClient({ endpoint: config.cosmosdbEndpoint, key: config.cosmosdbKey });
    const method = event.node.req.method;

    const database = client.database("SWAStore");
    const container = database.container("Items");

    if (method === "GET") {
        try {
            const { resources } = await container.items.readAll().fetchAll();
            return resources;
        } catch (error) {
            setResponseStatus(event, 500);
            return `Error retrieving items from the database: ${error.message}`;
        }
    } 
    // [ POST, PUT AND DELETE ENDPOINTS OMITTED FOR SIMPLICITY, AVAILABLE IN SOURCE CODE ]
    else {
        setResponseStatus(event, 405);
        return "Method Not Allowed";
    }
});

 

We now have CRUD endpoints for our Items, with the above snippet showing the GET endpoint and the POST, PUT, and DELETE implementations available in the source code. We can access our GET Items at http://localhost:3000/api/Items, which will return an array of our items.

Screenshot of GET request to /api/Items returning our array of itemsScreenshot of GET request to /api/Items returning our array of items

The rest of the CRUD endpoints for Sales entities will be available in the source code accompanying this article.

 

Create the pages in the Nuxt app

 

Now that we have an API for our Items and Sales, we can use these API endpoints within out Nuxt frontend application to provide the ability to see and edit the sales and items of our e-commerce store. Start by creating a file for the Items page at /pages/Items/index.vue with the following contents:

 

<template>
      <div>
        <div>
          <div v-for="item in items" :key="item.id">
            <div>
              <div>
                #{{ item.id }} - {{ item.title }}
              </div>
              <div>${{ item.price }}</div>
            </div>
            <div>
              <NuxtLink :to="`/items/edit/${item.id}`">Edit</NuxtLink>
              <button @click="handleDelete(item.id)">Delete</button>
            </div>
          </div>
        </div>
        <div>
          <NuxtLink to="/items/create">Create New Item</NuxtLink>
        </div>
      </div>
</template>

<script setup>
const { data: items, refresh: refreshItems } = await useFetch("/api/Items");

const handleDelete = async (itemId) => {
  const {data, error} = await fetch(`/api/Items/${itemId}`, {
    method: "DELETE",
  });
  if (error.value) {
    console.error("Error deleting item:", error);
  }
  // Trigger re-fetch after deletion
  refreshItems();
};
</script>

 

This page now presents our Items list within our application, by calling our Items API and displaying all items. We also added delete functionality, and we can further iterate on this application to add CRUD access for the Items and the Sales.

Screenshot of our full-stack Nuxt application with CRUD functionalityScreenshot of our full-stack Nuxt application with CRUD functionality

With our full-stack application complete, we can configure our application to make use of the rendering mode we want to leverage.

 

Configuring server-side rendering

 

By default, Nuxt.js provides universal rendering for every page, without additional configuration changes. This means that page requests that come from an external source, such as Google, will be rendered server-side, providing our application with better search engine optimization. Page requests that come from another page in our Nuxt application will make the fetch requests to our backend APIs instead, providing a smoother application experience. Universal rendering provides the best of both worlds, improved search engine optimization and initial application load, while providing SPA-level fluidity across pages.

 

Inspect the network tab within your browser for yourself, you'll see that fresh page loads respond with the HTML with data present, whereas page navigations within the application make API calls! Alternatively, we can configure the rendering mode within Nuxt to leverage server-side rendering or client-side rendering as we prefer. 

 

Deploying our Nuxt application to Azure Static Web Apps

 

We can easily deploy our Nuxt application to Azure Static Web Apps with the zero-configuration deployment. We’ll start by creating a GitHub repository for the nuxt-swa-full-stack-app project, which contains our Nuxt app.

 

Once the GitHub repository has been created, we can navigate to the Azure Portal and create a new Static Web Apps resource. In the deployment details, we must select the Nuxt build preset. This will create a GitHub Actions workflow file to build and deploy our application to your Static Web Apps resource with the proper configuration.

 

Finally, we set our database environment variables for our Azure Static Web Apps resource. From the Azure Static Web Apps resource in the Azure Portal, we navigate to Environment variables and set our NUXT_COSMOSDB_KEY as well as your NUXT_COSMOSDB_ENDPOINT.  Nuxt will automatically overwrite our runtimeConfig cosmosdbKey and cosmosdbEndpoint by convention.

 

And here is our final full-stack Nuxt application on Azure Static Web Apps: https://happy-beach-029f0310f.5.azurestaticapps.net/items :happyface:

 

Conclusion

 

We’ve finally built and deployed a full-stack Nuxt application with universal/server-side rendering to Azure Static Web Apps! This sample demonstrates how we can benefit from Nuxt’s zero-configuration deployments to Azure Static Web Apps and leverage Azure Static Web Apps’ distributed content host and serverless managed functions backends. Azure Static Web Apps also provides built-in preview environments and authorization to take this application to the next step, so make sure to try those out!

 

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

Co-Authors
Version history
Last update:
‎Mar 20 2024 12:27 PM
Updated by: