ASP.NET Web Forms True Pageless Architecture with Custom Session State

True Pageless: ASP.NET Web Forms Without a Single ASPX File

Implementing complete request interception at Global.asax with custom distributed session state


Introduction

In my previous article, Introducing ASP.NET Web Forms Pageless Architecture (WPA), I demonstrated how to build modern web applications on ASP.NET Web Forms by stripping away server controls, ViewState, and PostBack while using a single Default.aspx as the entry point.

But a question lingered: Is even that one ASPX file necessary?

The answer is no. By intercepting requests at Application_BeginRequest in Global.asax.cs, we can build a truly pageless architecture—zero ASPX files, zero page lifecycle, pure HTTP request handling.

This article demonstrates the complete implementation, including integration with a custom Session State Server built using the same WPA principles.


What “True Pageless” Means

Standard WPA vs True Pageless
Standard WPA Previous Article
Request IIS ASP.NET Pipeline Default.aspx Page_Load
Files: Global.asax, Global.asax.cs, Default.aspx, Default.aspx.cs
Page lifecycle: Yes (minimal, but exists)
True Pageless This Article
Request IIS ASP.NET Pipeline Application_BeginRequest
Files: Global.asax, Global.asax.cs
Page lifecycle: None
ASPX files: Zero

The Application_BeginRequest event fires before any page is instantiated. By handling all requests here and calling Response.End(), we bypass the entire page infrastructure.


Architecture Overview

True Pageless Architecture
HTTP Request
Global.asax.cs
Application_BeginRequest
  1. 1 Security check (local only for admin/api)
  2. 2 Load session from State Server
  3. 3 Route resolution
  4. 4 Handler dispatch
  5. 5 Response.End()
Public
Handlers
Admin
Handlers
API
Handlers
Two-Tier Cache
ConcurrentDictionary + Static File Cache
Custom Session State Server
(Separate WPA App) — Port 8090 (Local)

Why Bypass ASP.NET Session?

ASP.NET’s built-in Session has a limitation for true pageless architecture: it’s not available in Application_BeginRequest.

ASP.NET Request Pipeline
1. BeginRequest ◄── We intercept here (Session NOT available)
2. AuthenticateRequest
3. PostAuthenticateRequest
4. AuthorizeRequest
5. PostAuthorizeRequest
6. ResolveRequestCache
7. PostResolveRequestCache
8. MapRequestHandler
9. PostMapRequestHandler
10. AcquireRequestState ◄── Session loads here
11. PostAcquireRequestState ◄── Session available from here
12. PreRequestHandlerExecute
13. [Handler Executes] ◄── Default.aspx runs here
14. PostRequestHandlerExecute

To use ASP.NET Session, we’d have to wait until PostAcquireRequestState—which partially defeats the purpose of early interception.

Solution: Use a custom Session State Server that we can query at any point in the pipeline. This is where our WPA Session State Server comes in.


Project Structure

Project Structure
MySolution/
MyWebApp/ ← Main web application
Global.asax
Global.asax.cs ← ALL application logic here
Web.config
Core/
RouteResolver.cs ← URL parsing and routing
CacheStore.cs ← Two-tier cache
StateServerClient.cs ← Session state client
HandlerFactory.cs ← Handler instantiation
Handlers/
IHandler.cs
Public/
HomepageHandler.cs
ArticleHandler.cs
Admin/
DashboardHandler.cs
ArticlesHandler.cs
Api/
ArticlesApiHandler.cs
AuthApiHandler.cs
App_Data/
templates/ ← HTML templates
cache/ ← Static file cache
assets/ ← Static files (CSS, JS, images)
StateServer/ ← Session State Server (separate app)
Global.asax
Global.asax.cs ← State server logic
Web.config

Implementation

Global.asax

The Global.asax file remains minimal:

<%@ Application Language="C#" CodeBehind="Global.asax.cs" Inherits="MyWebApp.Global" %>

Global.asax.cs – The Complete Entry Point

using System;
using System.Collections.Concurrent;
using System.IO;
using System.Web;
using Newtonsoft.Json;

namespace MyWebApp
{
    public class Global : HttpApplication
    {
        #region Static Resources

        // Session state client (connects to our custom state server)
        private static StateServerClient _stateClient;

        // Page cache
        private static ConcurrentDictionary<string, CachedPage> _pageCache
            = new ConcurrentDictionary<string, CachedPage>();

        // Application start time
        private static DateTime _startTime;

        // Static file extensions to skip
        private static readonly HashSet<string> StaticExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
        {
            ".css", ".js", ".png", ".jpg", ".jpeg", ".gif", ".ico", ".svg",
            ".woff", ".woff2", ".ttf", ".eot", ".map", ".webp"
        };

        #endregion

        #region Application Lifecycle

        protected void Application_Start(object sender, EventArgs e)
        {
            _startTime = DateTime.UtcNow;

            // Initialize session state client
            _stateClient = new StateServerClient(
                serverUrl: "http://127.0.0.1:8090",
                appId: "my_web_app",
                secret: "your-secret-key-here"  // null if no signature required
            );

            // Verify state server connection
            if (!_stateClient.Ping())
            {
                // Log warning but don't fail - state server might start later
                System.Diagnostics.Debug.WriteLine("WARNING: State Server not available");
            }

            // Initialize cache store
            CacheStore.Initialize(Server.MapPath("~/App_Data/cache"));

            // Load route configurations, templates, etc.
            RouteResolver.Initialize(Server.MapPath("~/App_Data"));
        }

        #endregion

        #region Request Handling - THE CORE

        protected void Application_BeginRequest(object sender, EventArgs e)
        {
            string path = Request.Path.ToLower();

            // ════════════════════════════════════════════════════════════
            // STEP 1: Skip static files - let IIS handle them
            // ════════════════════════════════════════════════════════════

            if (IsStaticFile(path))
            {
                return; // IIS serves static files directly
            }

            // ════════════════════════════════════════════════════════════
            // STEP 2: Load session from custom State Server
            // ════════════════════════════════════════════════════════════

            var session = LoadSession();
            Context.Items["UserSession"] = session;

            // ════════════════════════════════════════════════════════════
            // STEP 3: Resolve route
            // ════════════════════════════════════════════════════════════

            var route = RouteResolver.Resolve(Request);

            // ════════════════════════════════════════════════════════════
            // STEP 4: Security checks
            // ════════════════════════════════════════════════════════════

            // Admin routes require authentication
            if (route.Type == RouteType.Admin)
            {
                if (session == null || !session.IsAdmin)
                {
                    if (route.Handler != "login")
                    {
                        Response.Redirect("/admin/login");
                        Response.End();
                        return;
                    }
                }
            }

            // ════════════════════════════════════════════════════════════
            // STEP 5: Handle request
            // ════════════════════════════════════════════════════════════

            try
            {
                switch (route.Type)
                {
                    case RouteType.Public:
                        HandlePublic(route);
                        break;

                    case RouteType.Admin:
                        HandleAdmin(route, session);
                        break;

                    case RouteType.Api:
                        HandleApi(route, session);
                        break;

                    case RouteType.Redirect:
                        Response.RedirectPermanent(route.RedirectTo);
                        break;

                    case RouteType.NotFound:
                    default:
                        Handle404();
                        break;
                }
            }
            catch (Exception ex)
            {
                HandleError(ex);
            }

            // ════════════════════════════════════════════════════════════
            // STEP 6: End response - bypass page lifecycle entirely
            // ════════════════════════════════════════════════════════════

            Response.End();
        }

        #endregion

        #region Session Management

        private UserSession LoadSession()
        {
            try
            {
                // Get session token from cookie
                string token = Request.Cookies["sid"]?.Value;

                if (string.IsNullOrEmpty(token))
                {
                    // New visitor - create token
                    token = Guid.NewGuid().ToString("N");
                    Response.Cookies.Add(new HttpCookie("sid", token)
                    {
                        HttpOnly = true,
                        Secure = Request.IsSecureConnection,
                        Expires = DateTime.Now.AddDays(30)
                    });
                    Context.Items["SessionToken"] = token;
                    return null;
                }

                Context.Items["SessionToken"] = token;

                // Load from state server
                return _stateClient.Get<UserSession>(token);
            }
            catch
            {
                return null;
            }
        }

        /// <summary>
        /// Save session - call this after login or session changes
        /// </summary>
        public static void SaveSession(HttpContext context, UserSession session)
        {
            string token = context.Items["SessionToken"] as string;
            if (!string.IsNullOrEmpty(token))
            {
                _stateClient.Set(token, session, timeoutMinutes: 60);
                context.Items["UserSession"] = session;
            }
        }

        /// <summary>
        /// Clear session - call this on logout
        /// </summary>
        public static void ClearSession(HttpContext context)
        {
            string token = context.Items["SessionToken"] as string;
            if (!string.IsNullOrEmpty(token))
            {
                _stateClient.Delete(token);
                context.Items["UserSession"] = null;
            }
        }

        #endregion

        #region Route Handlers

        private void HandlePublic(RouteResult route)
        {
            // Try cache first
            if (CacheStore.TryGetPage(route.CacheKey, out string html))
            {
                WriteHtml(html);
                return;
            }

            // Build page via handler
            var handler = HandlerFactory.CreatePublic(route.Handler);
            if (handler != null)
            {
                html = handler.Execute(Context, route);

                // Cache the result
                if (!string.IsNullOrEmpty(route.CacheKey))
                {
                    CacheStore.SetPage(route.CacheKey, html);
                }

                WriteHtml(html);
            }
            else
            {
                Handle404();
            }
        }

        private void HandleAdmin(RouteResult route, UserSession session)
        {
            var handler = HandlerFactory.CreateAdmin(route.Handler);
            if (handler != null)
            {
                string html = handler.Execute(Context, route, session);
                WriteHtml(html);
            }
            else
            {
                Handle404();
            }
        }

        private void HandleApi(RouteResult route, UserSession session)
        {
            Response.ContentType = "application/json";

            var handler = HandlerFactory.CreateApi(route.Handler);
            if (handler != null)
            {
                string json = handler.Execute(Context, route, session);
                Response.Write(json);
            }
            else
            {
                Response.StatusCode = 404;
                Response.Write("{\"success\":false,\"message\":\"API not found\"}");
            }
        }

        private void Handle404()
        {
            Response.StatusCode = 404;
            Response.ContentType = "text/html";

            // Try to load custom 404 template
            string html = TemplateEngine.Render("404", new { });
            Response.Write(html);
        }

        private void HandleError(Exception ex)
        {
            Response.StatusCode = 500;
            Response.ContentType = "text/html";

            #if DEBUG
            Response.Write($"<pre>{ex}</pre>");
            #else
            string html = TemplateEngine.Render("error", new { message = "An error occurred" });
            Response.Write(html);
            #endif
        }

        #endregion

        #region Helper Methods

        private bool IsStaticFile(string path)
        {
            // Check by extension
            string ext = Path.GetExtension(path);
            if (!string.IsNullOrEmpty(ext) && StaticExtensions.Contains(ext))
            {
                return true;
            }

            // Check by path prefix
            if (path.StartsWith("/assets/") ||
                path.StartsWith("/uploads/") ||
                path.StartsWith("/static/"))
            {
                return true;
            }

            return false;
        }

        private void WriteHtml(string html)
        {
            Response.ContentType = "text/html; charset=utf-8";
            Response.Write(html);
        }

        #endregion
    }

    #region Supporting Classes

    public class UserSession
    {
        public int UserId { get; set; }
        public string Username { get; set; }
        public bool IsAdmin { get; set; }
        public DateTime LoginTime { get; set; }
    }

    public class CachedPage
    {
        public string Html { get; set; }
        public DateTime CachedAt { get; set; }
        public DateTime? ExpiresAt { get; set; }
    }

    #endregion
}

Route Resolver

using System;
using System.Collections.Generic;
using System.IO;
using System.Web;

namespace MyWebApp
{
    public enum RouteType
    {
        Public,
        Admin,
        Api,
        Redirect,
        NotFound
    }

    public class RouteResult
    {
        public RouteType Type { get; set; }
        public string Handler { get; set; }
        public string Action { get; set; }
        public int ContentId { get; set; }
        public string CacheKey { get; set; }
        public string RedirectTo { get; set; }
        public Dictionary<string, string> Params { get; set; } = new Dictionary<string, string>();
    }

    public static class RouteResolver
    {
        private static HashSet<string> _adminRoutes;
        private static HashSet<string> _apiRoutes;
        private static HashSet<string> _specialRoutes;
        private static Dictionary<string, SlugInfo> _slugCache;

        public static void Initialize(string appDataPath)
        {
            // Define admin routes
            _adminRoutes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
            {
                "dashboard", "articles", "article-edit", "gallery", "media",
                "settings", "users", "login", "logout"
            };

            // Define API routes
            _apiRoutes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
            {
                "articles", "gallery", "media", "auth", "settings"
            };

            // Define special public routes
            _specialRoutes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
            {
                "", "home", "news", "events", "gallery", "contact", "about"
            };

            // Load slug cache from database
            _slugCache = LoadSlugsFromDatabase();
        }

        public static RouteResult Resolve(HttpRequest request)
        {
            // Parse URL
            string path = request.Path.Trim('/').ToLower();
            string[] segments = path.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);

            // Collect all parameters
            var parameters = CollectParameters(request);

            // ════════════════════════════════════════════════════════════
            // Check API routes: /api/{handler}/{action}
            // ════════════════════════════════════════════════════════════

            if (segments.Length > 0 && segments[0] == "api")
            {
                string handler = segments.Length > 1 ? segments[1] : "";
                string action = parameters.ContainsKey("action")
                    ? parameters["action"]
                    : (segments.Length > 2 ? segments[2] : "");

                if (_apiRoutes.Contains(handler))
                {
                    return new RouteResult
                    {
                        Type = RouteType.Api,
                        Handler = handler,
                        Action = action,
                        Params = parameters
                    };
                }

                return new RouteResult { Type = RouteType.NotFound };
            }

            // ════════════════════════════════════════════════════════════
            // Check Admin routes: /admin/{handler}/{action}/{id}
            // ════════════════════════════════════════════════════════════

            if (segments.Length > 0 && segments[0] == "admin")
            {
                string handler = segments.Length > 1 ? segments[1] : "dashboard";
                string action = parameters.ContainsKey("action")
                    ? parameters["action"]
                    : (segments.Length > 2 ? segments[2] : "");

                if (_adminRoutes.Contains(handler))
                {
                    var result = new RouteResult
                    {
                        Type = RouteType.Admin,
                        Handler = handler,
                        Action = action,
                        Params = parameters
                    };

                    // Extract ID
                    string idStr = parameters.ContainsKey("id")
                        ? parameters["id"]
                        : (segments.Length > 3 ? segments[3] : "");

                    if (int.TryParse(idStr, out int id))
                    {
                        result.ContentId = id;
                    }

                    return result;
                }

                return new RouteResult { Type = RouteType.NotFound };
            }

            // ════════════════════════════════════════════════════════════
            // Check special public routes
            // ════════════════════════════════════════════════════════════

            string primarySegment = segments.Length > 0 ? segments[0] : "";

            if (_specialRoutes.Contains(primarySegment))
            {
                string handler = string.IsNullOrEmpty(primarySegment) ? "homepage" : primarySegment;
                return new RouteResult
                {
                    Type = RouteType.Public,
                    Handler = handler,
                    CacheKey = $"page:{handler}",
                    Params = parameters
                };
            }

            // ════════════════════════════════════════════════════════════
            // Check content slugs (articles, pages)
            // ════════════════════════════════════════════════════════════

            if (_slugCache.TryGetValue(primarySegment, out var slugInfo))
            {
                // Handle old slugs with redirect
                if (!slugInfo.IsCurrent)
                {
                    return new RouteResult
                    {
                        Type = RouteType.Redirect,
                        RedirectTo = "/" + slugInfo.CurrentSlug
                    };
                }

                return new RouteResult
                {
                    Type = RouteType.Public,
                    Handler = slugInfo.ContentType,
                    ContentId = slugInfo.ContentId,
                    CacheKey = $"{slugInfo.ContentType}:{slugInfo.ContentId}",
                    Params = parameters
                };
            }

            // ════════════════════════════════════════════════════════════
            // Not found
            // ════════════════════════════════════════════════════════════

            return new RouteResult { Type = RouteType.NotFound };
        }

        private static Dictionary<string, string> CollectParameters(HttpRequest request)
        {
            var parameters = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

            // Query string
            foreach (string key in request.QueryString.AllKeys)
            {
                if (!string.IsNullOrEmpty(key))
                    parameters[key] = request.QueryString[key];
            }

            // Form data
            foreach (string key in request.Form.AllKeys)
            {
                if (!string.IsNullOrEmpty(key))
                    parameters[key] = request.Form[key];
            }

            // JSON body
            if (request.ContentType?.Contains("application/json") == true)
            {
                try
                {
                    request.InputStream.Position = 0;
                    using (var reader = new StreamReader(request.InputStream))
                    {
                        string json = reader.ReadToEnd();
                        var jsonParams = Newtonsoft.Json.JsonConvert
                            .DeserializeObject<Dictionary<string, object>>(json);

                        if (jsonParams != null)
                        {
                            foreach (var kvp in jsonParams)
                            {
                                parameters[kvp.Key] = kvp.Value?.ToString() ?? "";
                            }
                        }
                    }
                }
                catch { }
            }

            return parameters;
        }

        private static Dictionary<string, SlugInfo> LoadSlugsFromDatabase()
        {
            // Load from database - implementation depends on your data layer
            // Return dictionary for O(1) lookup
            return new Dictionary<string, SlugInfo>(StringComparer.OrdinalIgnoreCase);
        }
    }

    public class SlugInfo
    {
        public string Slug { get; set; }
        public string CurrentSlug { get; set; }
        public bool IsCurrent { get; set; }
        public string ContentType { get; set; }
        public int ContentId { get; set; }
    }
}

Handler Factory and Interfaces

using System;
using System.Collections.Generic;
using System.Web;

namespace MyWebApp
{
    public interface IPublicHandler
    {
        string Execute(HttpContext context, RouteResult route);
    }

    public interface IAdminHandler
    {
        string Execute(HttpContext context, RouteResult route, UserSession session);
    }

    public interface IApiHandler
    {
        string Execute(HttpContext context, RouteResult route, UserSession session);
    }

    public static class HandlerFactory
    {
        private static readonly Dictionary<string, Type> PublicHandlers = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase)
        {
            { "homepage", typeof(HomepageHandler) },
            { "article", typeof(ArticleHandler) },
            { "news", typeof(NewsHandler) },
            { "gallery", typeof(GalleryHandler) },
            { "contact", typeof(ContactHandler) }
        };

        private static readonly Dictionary<string, Type> AdminHandlers = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase)
        {
            { "dashboard", typeof(DashboardHandler) },
            { "articles", typeof(ArticlesHandler) },
            { "article-edit", typeof(ArticleEditHandler) },
            { "login", typeof(LoginHandler) },
            { "logout", typeof(LogoutHandler) }
        };

        private static readonly Dictionary<string, Type> ApiHandlers = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase)
        {
            { "articles", typeof(ArticlesApiHandler) },
            { "auth", typeof(AuthApiHandler) },
            { "media", typeof(MediaApiHandler) }
        };

        public static IPublicHandler CreatePublic(string name)
        {
            if (PublicHandlers.TryGetValue(name, out var type))
            {
                return (IPublicHandler)Activator.CreateInstance(type);
            }
            return null;
        }

        public static IAdminHandler CreateAdmin(string name)
        {
            if (AdminHandlers.TryGetValue(name, out var type))
            {
                return (IAdminHandler)Activator.CreateInstance(type);
            }
            return null;
        }

        public static IApiHandler CreateApi(string name)
        {
            if (ApiHandlers.TryGetValue(name, out var type))
            {
                return (IApiHandler)Activator.CreateInstance(type);
            }
            return null;
        }
    }
}

Example Handler: Auth API

using System;
using System.Web;
using Newtonsoft.Json;

namespace MyWebApp.Handlers.Api
{
    public class AuthApiHandler : IApiHandler
    {
        public string Execute(HttpContext context, RouteResult route, UserSession session)
        {
            string action = route.Action.ToLower();

            switch (action)
            {
                case "login":
                    return HandleLogin(context, route);

                case "logout":
                    return HandleLogout(context);

                case "check":
                    return HandleCheck(session);

                default:
                    return JsonConvert.SerializeObject(new { success = false, message = "Unknown action" });
            }
        }

        private string HandleLogin(HttpContext context, RouteResult route)
        {
            string username = route.Params.GetValueOrDefault("username", "");
            string password = route.Params.GetValueOrDefault("password", "");

            // Validate credentials (implement your logic)
            var user = ValidateCredentials(username, password);

            if (user == null)
            {
                return JsonConvert.SerializeObject(new { success = false, message = "Invalid credentials" });
            }

            // Create session
            var session = new UserSession
            {
                UserId = user.Id,
                Username = user.Username,
                IsAdmin = user.IsAdmin,
                LoginTime = DateTime.UtcNow
            };

            // Save to state server
            Global.SaveSession(context, session);

            return JsonConvert.SerializeObject(new
            {
                success = true,
                message = "Login successful",
                user = new { user.Id, user.Username, user.IsAdmin }
            });
        }

        private string HandleLogout(HttpContext context)
        {
            Global.ClearSession(context);

            return JsonConvert.SerializeObject(new { success = true, message = "Logged out" });
        }

        private string HandleCheck(UserSession session)
        {
            if (session == null)
            {
                return JsonConvert.SerializeObject(new { success = true, authenticated = false });
            }

            return JsonConvert.SerializeObject(new
            {
                success = true,
                authenticated = true,
                user = new { session.UserId, session.Username, session.IsAdmin }
            });
        }

        private User ValidateCredentials(string username, string password)
        {
            // Implement your authentication logic
            // Query database, verify password hash, etc.
            return null;
        }
    }

    public class User
    {
        public int Id { get; set; }
        public string Username { get; set; }
        public bool IsAdmin { get; set; }
    }
}

The Complete Stack

With this architecture, you have two WPA applications working together:

Production Deployment
Main Web App (WPA) Port 80/443 (public)
  • Zero ASPX files
  • Application_BeginRequest handles all
  • Custom routing, caching, templates
HTTP/JSON (localhost only)
Session State Server (WPA) Port 8090 (local only – 127.0.0.1)
  • Zero ASPX files
  • Application_BeginRequest handles all
  • Multi-tenant session storage
Background queue (optional)
MySQL Port 3306
  • Session persistence (optional)
  • Content storage

Key Benefits

1. Zero Page Lifecycle Overhead

Request Lifecycle Comparison
Traditional Web Forms Request
PreInit Init InitComplete PreLoad Load LoadComplete
PreRender PreRenderComplete SaveStateComplete Render
True Pageless Request
BeginRequest Your Code Response.End()
No page instantiation, no control tree, no ViewState processing.

2. Earliest Possible Interception

You control the request from the very first event in the pipeline.
Nothing happens before Application_BeginRequest.

3. Custom Session Without Pipeline Dependency

ASP.NET Session requires waiting until AcquireRequestState.
Custom State Server is available immediately in BeginRequest.

4. Consistent Architecture

Unified Pattern
Main App
Global.asax.cs Application_BeginRequest Handlers
State Server
Global.asax.cs Application_BeginRequest Handlers

When to Use This Architecture

Use True Pageless when:

  • You want maximum control over the request pipeline
  • You need session data at the earliest point possible
  • You’re building a new project without legacy ASPX pages
  • You want architectural consistency across multiple apps
  • Performance is critical and you want to eliminate all overhead

Stick with Default.aspx entry point when:

  • You have existing ASPX pages to maintain alongside new code
  • You need ASP.NET Session (available at PostAcquireRequestState)
  • Team familiarity with Page_Load pattern is important
  • Debugging with page-based breakpoints is preferred

Conclusion

True Pageless Architecture takes ASP.NET Web Forms to its logical conclusion: if we’re not using server controls, ViewState, or PostBack, why use pages at all?

By intercepting requests at Application_BeginRequest and integrating a custom Session State Server built with the same principles, we create a fully consistent, high-performance architecture with zero ASPX files.

This isn’t about fighting the framework—it’s about recognizing that the HTTP processing engine underneath Web Forms is solid, and building directly on that foundation.

The result is a clean, fast, and fully transparent architecture where every request follows the same path: HTTP in, HTML/JSON out, nothing hidden.


Further Reading


This article demonstrates a proof of concept for True Pageless Architecture. The code examples provide a foundation that can be extended based on specific project requirements.