Hosting a SvelteKit app with ASP.NET Core

Table of contents

Why Svelte-Kit?

In this article, we'll create a single-page application (SPA) using Svelte-Kit and ASP.NET Core 7. First, let's talk about the advantages of this setup:

  • SEO-friendly: We'll be using static site generation (SSG) together with ASP.NET Core 7, which is good for establishing better SEO scores.
  • Super performant: Svelte-kit is known for its performance, which is a win-win for SEO and users.

Setting up the project

  • First, we create our project using the SPA template dotnet new react.
  • Next, we don't need the ClientApp folder, so we'll start by deleting it. On a *nix terminal, we can use rm -rf ClientApp.
  • Now, we have everything we need to start installing our SvelteKit app:
npm create svelte@latest ClientApp
cd ClientApp
npm install

Development Proxy

Now, we'll configure the vite proxy for our ASP.NET Core API:

  • Look for launchSettings.json; In the applicationUrlparameter, there are the URLs the application is configured to use in development mode. We can delete the one that uses https because we won't be dealing with SSL for development now.
  • The http port in this case is 5125, let's configure our SvelteKit proxy to use this port.

Vite allows to proxying requests, so not much work is needed. We can achieve that by adding the following to our vite.config.js

server: {
    proxy: {
        '/api': 'http://localhost:5125'
    }
}

The configuration file now looks like this:

import { sveltekit } from '@sveltejs/kit/vite';

/** @type {import('vite').UserConfig} */
const config = {
	plugins: [sveltekit()],
	server: {
		proxy: {
			'/api': 'http://localhost:5125'
		}
	}
};

export default config;

Svelte-Kit uses Vite, we need to update the command that runs our development frontend in the .csproj as follows:

<SpaProxyLaunchCommand>vite dev</SpaProxyLaunchCommand>

When running vite dev, the app runs on port 5173. Let's define that in the csproj file as well, for our convenience, in the SpaProxyServerUrl property.

<SpaProxyServerUrl>http://localhost:5173</SpaProxyServerUrl>

Change the WeatherForecastController.cs route name to [Route("api/controller")] which will allow us to fetch the dummy data from our SvelteKit app using our proxy configuration.

That's it! We can now run our ASP.NET Core SPA project, with SvelteKit, by using dotnet run and we can see the app running effectively and the routes being applied.

After setting up our project to run effectively in development mode, let's focus on the publishing part, which is when most of the magic happens, and when ASP.NET Core serves our SPA app.

Configuring the Svelte-Kit adapter

The adapter is responsible for the publishing part of the frontend. You can learn more about it on the Svelte-Kit adapters documentation page for the intricacies of how it functions in a production build. We'll use the following setup:

  • Using build folder to place all the needed files for the publishing part.
  • The project is already configured to copy the build folder's contents to wwwroot which is where the actual files will be after running dotnet publish.

Our adapter of choice is @sveltejs/adapter-static which is adequate for static-generation. First, install the package using npm install @sveltejs/adapter-static, then we change the svelte.config.js file to use this adapter:

import adapter from '@sveltejs/adapter-static';

/** @type {import('@sveltejs/kit').Config} */
const config = {
	
	kit: {
		adapter: adapter({assets: 'build', pages: 'build'})
	}
};

export default config;

This will tell SvelteKit where to publish the actual content. In our setup, we are using the build folder since it's the folder our ASP.NET Core project already uses for publishing the React contents (line 47).

<!-- Include the newly-built files in the publish output -->
        <ItemGroup>
            <DistFiles Include="$(SpaRoot)build\**" />
            <ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
                <RelativePath>wwwroot\%(RecursiveDir)%(FileName)%(Extension)</RelativePath>
                <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
                <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
            </ResolvedFileToPublish>
        </ItemGroup>

Remove the Sverdle game from the frontend project. Remove the sverdle folder and remove the link to it in the layout.svelte.

Now, we need to change the build command that the project files use to use Vite instead, so on line 44, change npm run build to vite build:

<!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
<Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
<Exec WorkingDirectory="$(SpaRoot)" Command="vite build" />

Final touches

After the generation, for each route, we'll have a .html file. In our case, for the Index and About pages, everything should work now from the index page, but not from the about page. To have everything working, we must map the route to the file in our Program.cs:

app.MapFallbackToFile("about", "about.html");

Our Program.cs looks like this now:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllersWithViews();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();


app.MapControllerRoute(
    name: "default",
    pattern: "{controller}/{action=Index}/{id?}");

app.MapFallbackToFile("about", "about.html");
app.MapFallbackToFile("index.html");

app.Run();

Sample code

In order to see the complete project used in this example, check out AspNetSPA_SvelteKit GitHub repository.

Related Content
Author
I’m a passionate full-stack software engineer, architect and Debian lover.