My blog Admin Panel - developing with a Mini-SPA

The Admin Panel of my blog is a Mini-SPA. It consists of a lists of Razor Pages which refreshes going from page to page, but each page contains JavaScript code and components, in my case Vue and Vuetify, that make it behave like a SPA. This architecture tries to combine the best of both multi-page and single-page apps, it has a lower learning curve and allows people to get started more quickly. But, on the other hand it does have a degree of hacky-ness that goes with it, so here I want to jot down its basic architecture and development process. By no mean exhaustive, but hopefully this can help others understand the code better. 


This architectural diagram below shows the different pieces. On the outset there's a layout page (Fan.WebApp > Manage > Admin > Shared > _Layout.cshtml) that each individual page will use. At the launch of each page, the OnGetAsync method from that page's code behind will execute to initialize a Vue mixin object living on the bottom of the page. The mixin object will become part of the Vue component living in its own dedicated JS file for that page right after the mixin initialization.

Fanray Admin Panel - Mini-SPA Architecture
Fanray Admin Panel - Mini-SPA Architecture

What user is dealing with on each page are Vuetify components, and a more complex user interaction is opening up a modal dialog. For example, on the Navigation.cshtml when user clicks the Edit icon on an navigation menu item, a modal dialog box will appear. What lives inside this dialog is another Razor Page (Fan.WebApp > Manage > Shared > NavSettings.cshtml) with its own layout (Fan.WebApp > Manage > _SettingsLayout.cshtml). And architecturally this settings page is no different than the Navigation page itself.

Admin Panel - Navigation page and Modal Dialog
Admin Panel - Navigation page and Modal Dialog

Here is what's on the bottom of the Navigation.cshtml, the PagesJson is one of the C# properties in the Navigation.cshtml.cs and is initialized by its OnGetAsync method.

    let navigationMixin = {
        data: function () {
            return {
                pages: @Html.Raw(Model.PagesJson),
<script src="~/admin/js/navigation.js" asp-append-version="true"></script>

Here is part of that OnGetAsync method.

public async Task OnGetAsync()
    var pages = await pageService.GetParentsAsync(withChildren: false);
    PagesJson = JsonConvert.SerializeObject(
        from page in pages
        where page.Status != Blog.Enums.EPostStatus.Draft
        select new { page.Id, Text = page.Title, Type = ENavType.Page }

JS Code

The bulk of the logic is stored in a separate Fan.WebApp > Manage > Admin > client > navigation.js file which is a Vue component that supports the component on the Navigation.cshtml. During development I do the following, BTW I have all the commands written in a placed in the Admin folder. 

Right click at the Fan.WebApp > Manage > Admin > client folder and choose Open in Terminal.

Admin Panel - open Terminal at the client folder
Admin Panel - open Terminal at the client folder

In the open terminal inside VS, run the following command. It runs babel with watch to pick up immediately any changes I make.

babel js/navigation.js --out-dir ../../../wwwroot/admin/js --source-maps --watch
Admin Panel - run babel cmd in Developer Command Prompt
Admin Panel - run babel cmd in Developer Command Prompt

The dev version of the js file has console.log statements in it. When development is done, run this command to remove the log statements. The production version of the same file is stored at Fan.WebApp > wwwroot > admin > js folder.

babel js/navigation.js --out-dir ../../../wwwroot/admin/js --source-maps --plugins transform-remove-console


In this post I laid out the architecture of my blog's Admin Panel, this should give you the basic understanding how things work. At this point figuring out the inner workings of each page should be easy, assuming you know the technologies used Vue, Vuetify and Razor Pages.

There are a couple of things I'd like to improve in the future. 

  • The Admin Panel right now lives inside the Fan.WebApp project, it may be a good idea to move it out into its own project while things grow.
  • There is more logic residing in the Razor code behind files than I'd have liked, I could refactor some of the logic out into their own service classes.
  • Should I find a Blazor component library that is as capable as Vuetify (it's a high bar to match for sure), I would try to migrate this portion of the application to server-side blazor.