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)](https://qiita.com/takashiuesaka/items/e167852af299a7b00939)

> 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](/plants/nextjs-aspnet-core-yarp-aspire/image-0.1.png)

### 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](/plants/nextjs-aspnet-core-yarp-aspire/image-1.png)

## 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

```powershell
npx create-next-app@latest
```

- Settings during command execution (for reference)

```powershell
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](/plants/nextjs-aspnet-core-yarp-aspire/image-2.png)

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](/plants/nextjs-aspnet-core-yarp-aspire/image-3.png)

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

```cs
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.

```cs
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.

```tsx
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.

```tsx
'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](/plants/nextjs-aspnet-core-yarp-aspire/image-4.png)

### Implement the reference in the AppHost project

Modify `NextJSAspire.AppHost\Program.cs` as follows.

```cs
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](/plants/nextjs-aspnet-core-yarp-aspire/image-6.png)

### Implement the reverse proxy

Implement `ReverseProxy\Program.cs` as follows.

```cs
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.

```cs
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.

```tsx
'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.
