paint-brush
Essential Guide to Running Nuxt from an ASP.NET Core Web Applicationby@samwalpole
157 reads

Essential Guide to Running Nuxt from an ASP.NET Core Web Application

by Sam WalpoleJanuary 1st, 2021
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

It is becoming a common pattern to see websites and web apps written as a front end single page application (SPA) connected to a backend API. For this reason, the Visual Studio provides a several project templates for getting up and going with a Web API + SPA project.

Companies Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - Essential Guide to Running Nuxt from an ASP.NET Core Web Application
Sam Walpole HackerNoon profile picture

It is becoming a common pattern to see websites and web apps written as a front end single page application (SPA) connected to a backend API. For this reason, the Visual Studio provides a several project templates for getting up and going with a Web API + SPA project.

However, at time of writing, these project templates only support Angular and React. They completely miss out on supporting Vue projects, despite it being one of the big 3 frontend frameworks.

Of course, it is totally possible to just run your Vue project and ASP.NET Core project separately, but this is less than ideal for a number of reasons. Firstly, it means having multiple servers running, one for your each application, but you'll also run into issues with CORS , SSL, and doing things with cookies such as protecting against CRSF becomes trickier.

In my opinion, it is a much more ideal situation to have you SPA being served by the same application that provides your API.

Thankfully someone has written an in depth article on how to serve a Vue SPA from an ASP.NET Core web application. However, I was recently working on a project using Nuxt (which is based on Vue), and I had to make some adjustments to the article to get it to work with Nuxt. If you're looking at how to integrate your ASP.NET Core web application with Nuxt, please keep reading.

How to run Nuxt from a ASP.Net Core Web Application

Start by creating a new ASP.NET Core Web Application and select the API project template.

From a terminal window navigate to the main folder of your project (this will be the same folder that has your Program.cs and Startup.cs files). You can install Nuxt using either NPM or Yarn, depending on your preference. Please note that the rest of the tutorial relies on the Nuxt app being called client-app, so please follow that instruction.

# install using NPM
npx create-nuxt-app client-app

# OR

# install using Yarn
yarn create nuxt-app client-app

Follow through the installation instructions, selecting the various addons that you desire. For the purpose of this tutorial, I just selected all of the default options.

Once that is installed, we need to modify the projects *.csproj file. In Visual Studio, you can do this by double clicking on the project name. The following markup will allow the Nuxt files to be built and published properly when the main ASP.NET Core project is built. Your *.csproj file should look like this:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
    <TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
    <IsPackable>false</IsPackable>
    <SpaRoot>client-app\</SpaRoot>
    <DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\**</DefaultItemExcludes>
  </PropertyGroup>


  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="3.0.0-preview6.19307.2" />
  </ItemGroup>

  <ItemGroup>
    <!-- Don't publish the SPA source files, but do show them in the project files list -->
    <Content Remove="$(SpaRoot)**" />
    <None Remove="$(SpaRoot)**" />
    <None Include="$(SpaRoot)**" Exclude="$(SpaRoot)node_modules\**" />
  </ItemGroup>

  <Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') ">
    <!-- Ensure Node.js is installed -->
    <Exec Command="node --version" ContinueOnError="true">
      <Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
    </Exec>
    <Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE." />
    <Message Importance="high" Text="Restoring dependencies using 'npm'. This may take several minutes..." />
    <Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
  </Target>

  <Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
    <!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
    <Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
    <Exec WorkingDirectory="$(SpaRoot)" Command="npm run generate" />

    <!-- Include the newly-built files in the publish output -->
    <ItemGroup>
      <DistFiles Include="$(SpaRoot)dist\**" />
      <ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
        <RelativePath>%(DistFiles.Identity)</RelativePath>
        <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
      </ResolvedFileToPublish>
    </ItemGroup>
  </Target>
</Project>

After that, we need to create a helper class that will allow the Nuxt development server to be proxied by the ASP.NET Core web application. This helper is only useful in development, since in production the Nuxt project will be being served by the server as static files.

In your main project folder, create a folder called Helpers and inside create a file called NuxtHelper.cs:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.SpaServices;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.NetworkInformation;
using System.Runtime.InteropServices;
using System.Threading.Tasks;

namespace NuxtIntegration.Helpers
{
    public static class NuxtHelper
    {
        // default port number of 'npm run dev'
        private static int Port { get; } = 3000;
        private static Uri DevelopmentServerEndpoint { get; } = new Uri($"http://localhost:{Port}");
        private static TimeSpan Timeout { get; } = TimeSpan.FromSeconds(30);
        // done message of 'npm run dev' command.
        private static string DoneMessage { get; } = "DONE  Compiled successfully in";

        public static void UseNuxtDevelopmentServer(this ISpaBuilder spa)
        {
            spa.UseProxyToSpaDevelopmentServer(async () =>
            {
                var loggerFactory = spa.ApplicationBuilder.ApplicationServices.GetService<ILoggerFactory>();
                var logger = loggerFactory.CreateLogger("Nuxt");
                // if 'npm dev' command was executed yourself, then just return the endpoint.
                if (IsRunning())
                {
                    return DevelopmentServerEndpoint;
                }

                // launch Nuxt development server
                var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
                var processInfo = new ProcessStartInfo
                {
                    FileName = isWindows ? "cmd" : "npm",
                    Arguments = $"{(isWindows ? "/c npm " : "")}run dev",
                    WorkingDirectory = "client-app",
                    RedirectStandardError = true,
                    RedirectStandardInput = true,
                    RedirectStandardOutput = true,
                    UseShellExecute = false,
                };
                var process = Process.Start(processInfo);
                var tcs = new TaskCompletionSource<int>();
                _ = Task.Run(() =>
                {
                    try
                    {
                        string line;
                        while ((line = process.StandardOutput.ReadLine()) != null)
                        {
                            logger.LogInformation(line);
                            if (!tcs.Task.IsCompleted && line.Contains(DoneMessage))
                            {
                                tcs.SetResult(1);
                            }
                        }
                    }
                    catch (EndOfStreamException ex)
                    {
                        logger.LogError(ex.ToString());
                        tcs.SetException(new InvalidOperationException("'npm run dev' failed.", ex));
                    }
                });
                _ = Task.Run(() =>
                {
                    try
                    {
                        string line;
                        while ((line = process.StandardError.ReadLine()) != null)
                        {
                            logger.LogError(line);
                        }
                    }
                    catch (EndOfStreamException ex)
                    {
                        logger.LogError(ex.ToString());
                        tcs.SetException(new InvalidOperationException("'npm run dev' failed.", ex));
                    }
                });

                var timeout = Task.Delay(Timeout);
                if (await Task.WhenAny(timeout, tcs.Task) == timeout)
                {
                    throw new TimeoutException();
                }

                return DevelopmentServerEndpoint;
            });

        }

        private static bool IsRunning() => IPGlobalProperties.GetIPGlobalProperties()
                .GetActiveTcpListeners()
                .Select(x => x.Port)
                .Contains(Port);
    }
}

Please note that this presumes you are running Nuxt on the default port of 3000. If you are using a different port, you can update the Port property in the NuxtHelper class.

Finally, we need to configure the Startup.cs file to use this helper during development and to serve the Nuxt files during production. Go to the ConfigureServices method and add the following line:

services.AddSpaStaticFiles(options => options.RootPath = "client-app/dist");

Then go to the Configure method and add the following lines after the app.UseEndpoints statement:

            app.UseSpaStaticFiles();
            app.UseSpa(spa =>
            {
                spa.Options.SourcePath = "client-app";
                if (env.IsDevelopment())
                {
                    // Launch development server for Nuxt
                    spa.UseNuxtDevelopmentServer();
                }
            });

Everything is now set up! Run your application and go to the root page in your browser (i.e. https://localhost:{port}). You should be able to see your Nuxt app home page there.

Occasionally (especially on the first build), you might get a TimeoutException: The operation has timed out message. This is usually because the Nuxt project is taking longer to build than expected. Just wait a few moments then refresh the page. That should fix it.

Conclusion

In this article, I have shown you how to run a Nuxt project from a ASP.NET Core Web Application. This is based on the information given here for integrating Vue projects.

I have made a public GitHub repo available of this project. Please feel free to fork it to help you get started with your own Nuxt/ASP.NET Core project.

I post mostly about full-stack .NET and Vue web development. To make sure that you don't miss out on any posts, please follow this blog and subscribe to my newsletter. If you found this post helpful, please like it and share it. You can also find me on Twitter.