Configuring Next.js + ASP.NET Core + YARP with .NET Aspire

How to configure Next.js + ASP.NET Core + YARP (Reverse Proxy) with .NET Aspire

TechPublished 3 min read

This article explains how to create a project that combines Next.js and ASP.NET Core, and use YARP (Yet Another Reverse Proxy) to connect the frontend and backend. Here's the overall flow.

  1. Build a Web API using ASP.NET Core.
  2. Build a frontend using Next.js.
  3. Manage the project with .NET Aspire and implement a reverse proxy with YARP.
  4. Explain how to call the ASP.NET Core Web API from Next.js.

The source code for this article is here (https://github.com/atman-33/nextjs-aspire)

Reference URL

The detailed steps are based on the following article:
Configuring Next.js + ASP.NET Core with .NET Aspire (with YARP)

The setup steps on the site above didn't resolve a CORS error that occurred when calling the Web API from a Next.js CSR page, so I hope this article helps as a fix for that error.

1. Create the Web API with ASP.NET Core

Create an empty solution

First, create an empty solution. We'll name the solution NextJSAspire.

Empty solution

Create the ASP.NET Core project

Next, create an ASP.NET Core Web API project. We'll name the project WebApi.

ASP.NET Core Web API

2. Create the frontend with Next.js

Open the directory containing the solution file in PowerShell, and run the following command to create the Next.js application.

  • Next.js generation command
npx create-next-app@latest
  • Settings during command execution (for reference)
PS C:\Repos\nextjs-aspire> npx create-next-app@latest
Need to install the following packages:
create-next-app@14.2.3
Ok to proceed? (y) y
√ What is your project named? ... frontend
√ Would you like to use TypeScript? ... Yes
√ Would you like to use ESLint? ... Yes
√ Would you like to use Tailwind CSS? ... Yes
√ Would you like to use `src/` directory? ... Yes
√ Would you like to use App Router? (recommended) ... Yes
√ Would you like to customize the default import alias (@/*)? ... Yes
√ What import alias would you like configured? ... @/*
Creating a new Next.js app in C:\Repos\nextjs-aspire\frontend.

3. Bring the Next.js application under .NET Aspire management

To manage a Node.js project with .NET Aspire, you need a library reference. Right-click the AppHost project, and select "Add" → ".NET Aspire Package".

.NET Aspire Package

In the NuGet Package Manager screen that appears, add "Node" to the search string, and install the package for hosting Node.js.

NuGet Package Manager

Implement the AppHost project's Program.cs as follows.

using Microsoft.Extensions.Hosting;

var builder = DistributedApplication.CreateBuilder(args);

var api = builder.AddProject<Projects.WebApi>("webapi");

var frontend = builder.AddNpmApp(name: "frontend", workingDirectory: "../frontend", scriptName: "dev")
    .WithHttpEndpoint(env: "PORT")
    .WithExternalHttpEndpoints()
    .WithReference(api);

if (builder.Environment.IsDevelopment() && builder.Configuration["DOTNET_LAUNCH_PROFILE"] == "https")
{
    frontend.WithEnvironment("NODE_TLS_REJECT_UNAUTHORIZED", "0");
}

builder.Build().Run();

4. Call the Web API from Next.js

Change the Web API endpoint

Before implementing this in Next.js, change the Web API's endpoint. Modify the WebApi project's Program.cs as follows.

var builder = WebApplication.CreateBuilder(args);

builder.AddServiceDefaults();

var app = builder.Build();

app.MapDefaultEndpoints();

app.UseHttpsRedirection();

var summaries = new[]
{
    "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};

// app.MapGet("weatherforecast", () =>
app.MapGet("api/weatherforecast", () => // <= api/ を追加
{
    var forecast = Enumerable.Range(1, 5).Select(index =>
        new WeatherForecast
        (
            DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
            Random.Shared.Next(-20, 55),
            summaries[Random.Shared.Next(summaries.Length)]
        ))
        .ToArray();
    return forecast;
});

app.Run();

record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}

Call the Web API from Server Components

Modify frontend\src\app\server\page.tsx as follows.

const getData = async () => {
    const apiServer = process.env['services__webapi__https__0'] ?? process.env['services__webapi__http__0'];
    const weatherData: Response = await fetch(`${apiServer}/api/weatherforecast`, { cache: 'no-cache' });

    if (!weatherData.ok) {
        throw new Error('Failed to fetch data.');
    }

    const data = await weatherData.json();
    return data;
}

const Page = async () => {
    const data = await getData();
    return <main>{JSON.stringify(data)}</main>;
}

export default Page;

Call the Web API from Client Components

Modify frontend\src\app\client\page.tsx as follows.

'use client'

import { useEffect, useState } from 'react';

const getData = async () => {
  // NOTE:  WebApi プロジェクトの launchSettings.json ファイル > profiles > http > applicationUrl
  // "applicationUrl": "http://localhost:5291",
  const weatherData: Response = await fetch('http://localhost:5291/api/weatherforecast', { cache: 'no-cache' });

  if (!weatherData.ok) {
    throw new Error('Failed to fetch data.');
  }

  const data = await weatherData.json();
  return data;
}

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

  useEffect(() => {
    getData().then((data) => setData(data));
  }, []);

  return <main>{JSON.stringify(data)}</main>;
}

export default ClientPage;

At this point, a CORS error occurs, so in the next step we'll implement a reverse proxy.

5. Implement a reverse proxy with YARP

Add an empty ASP.NET Core application

Add an ASP.NET Core (empty) project. We'll name the project ReverseProxy.

ASP.NET Core Empty Project

Implement the reference in the AppHost project

Modify NextJSAspire.AppHost\Program.cs as follows.

using Microsoft.Extensions.Hosting;

var builder = DistributedApplication.CreateBuilder(args);

var api = builder.AddProject<Projects.WebApi>("webapi");

var frontend = builder.AddNpmApp(name: "frontend", workingDirectory: "../frontend", scriptName: "dev")
    .WithHttpEndpoint(env: "PORT")
    .WithExternalHttpEndpoints()
    .WithReference(api);

if (builder.Environment.IsDevelopment() && builder.Configuration["DOTNET_LAUNCH_PROFILE"] == "https")
{
    frontend.WithEnvironment("NODE_TLS_REJECT_UNAUTHORIZED", "0");
}

builder.AddProject<Projects.ReverseProxy>("reverseproxy")
    .WithReference(frontend)
    .WithReference(api);

builder.Build().Run();

Install Microsoft.Extensions.ServiceDiscovery.Yarp

  1. Open the ReverseProxy project's NuGet package management screen.
  2. Install Microsoft.Extensions.ServiceDiscovery.Yarp.

NuGet Package Manager

Implement the reverse proxy

Implement ReverseProxy\Program.cs as follows.

using Yarp.ReverseProxy.Configuration;

var builder = WebApplication.CreateBuilder(args);

// NOTE: ServiceDiscovery を有効とする。
// AppHost プロジェクトで実装した Next.js と WebAPI プロジェクトへの参照を SerivceDiscovery で解決できるようになるが、
// YARP の場合は、これだけでは、ServiceDiscovery ができない。
builder.AddServiceDefaults();

// NOTE: AddReverseProxy は Yarp.ReverseProxy パッケージに含まれている YARP を使用することをパイプラインに適用するためのメソッド。
// Add したら Use するのがパイプラインの基本なので、Use を後で実装している(今回は、Map~ が Use に相当する)。
builder.Services.AddReverseProxy()
    .LoadFromMemory(GetRoutes(), GetClusters())
    .AddServiceDiscoveryDestinationResolver();

var app = builder.Build();

app.MapDefaultEndpoints();

// NOTE: パイプラインのUse
app.MapReverseProxy();

app.Run();

// GetRoutes メソッドで振り分けのルールを定義
RouteConfig[] GetRoutes()
{
    return
    [
        new RouteConfig
        {
            RouteId = "Route1",
            ClusterId = "default",
            Match = new RouteMatch { Path = "{**catch-all}" }
        },
        new RouteConfig
        {
            RouteId = "Route2",
            ClusterId = "api",
            Match = new RouteMatch { Path = "/api/{*any}" }
        },
    ];
}

// GetClusters メソッドで振り分け先のパスを定義
ClusterConfig[] GetClusters()
{
    return
    [
        new ClusterConfig
        {
            ClusterId = "default",
            Destinations = new Dictionary<string, DestinationConfig>
            {
                { "destination1", new DestinationConfig { Address = "http://frontend" } },
            }
        },
        new ClusterConfig
        {
            ClusterId = "api",
            Destinations = new Dictionary<string, DestinationConfig>
            {
                { "destination2", new DestinationConfig { Address =  "http://webapi", Host = "localhost" } },
            }
        },
    ];
}

Allow access from the reverse proxy on the Web API

Modify WebApi\Program.cs as follows.

var builder = WebApplication.CreateBuilder(args);

builder.AddServiceDefaults();

// ---- 追加 ここから ---- //
builder.Services.AddCors(x =>
{
    x.AddDefaultPolicy(policy =>
    {
        policy.AllowAnyHeader();
        policy.AllowAnyMethod();
        policy.AllowCredentials();
        policy.WithOrigins(new string[] { "https://localhost:7213", "http://localhost:5172" });
        // NOTE: 全ての Origin を許可する場合は、下記コード
        // policy.SetIsOriginAllowed(origin => true); 
    });
});
// ---- 追加 ここまで ---- //

// Add services to the container.

var app = builder.Build();

// ---- 追加 ここから ---- //
app.UseCors();  // CORS有効のために追加
// ---- 追加 ここまで ---- //

app.MapDefaultEndpoints();

...

Fix the Web API call from Next.js CSR

Modify frontend\src\app\client\page.tsx as follows.

'use client'

import { useEffect, useState } from 'react';

const getData = async () => {
  const weatherData: Response = await fetch('/api/weatherforecast', { cache: 'no-cache' }) // <= ホスト名を削除

  ...
}

Now you can fetch data from the Web API through the reverse proxy. The dashboard now shows an extra ReverseProxy resource — click the ReverseProxy's endpoint link to display the initial screen, then go to /client to check whether you can fetch data from the Web API.