Building a data table in Blazor is usually straightforward. QuickGrid takes care of much of the work for you, including displaying columns, sorting data, and handling pagination.

The situation becomes a little more interesting when the application uses Static Server-Side Rendering (Static SSR).

With an interactive Blazor application, a component can react to events such as button clicks and update its state without a full navigation. Static SSR works differently. The server renders the component, and the browser receives the resulting HTML.

So how can a sortable QuickGrid still work without relying on interactive component events?

The answer is URL-based sorting and pagination.

Instead of keeping the sorting state only inside the component, QuickGrid can represent that state in the query string. A sortable column can navigate to a URL such as:

/products?sort=Price&order=desc

The server receives the request, QuickGrid reads the sorting information, and the page is rendered again using the requested order.

This approach fits naturally with Static SSR because the browser is simply following a URL.

Understanding Static SSR

Static SSR means that the server generates the HTML for a Blazor component and sends it to the browser.

The browser does not need an interactive Blazor connection just to display the rendered page.

A simplified request looks like this:

Browser
   |
   | HTTP Request
   v
Blazor Server
   |
   | Render Component
   v
HTML Response
   |
   v
Browser

If the user clicks a normal link, another HTTP request can be made:

Browser
   |
   | /products?sort=Price&order=desc
   v
Blazor Server
   |
   | Render sorted data
   v
Browser

This is different from an interactive event:

Browser
   |
   | @onclick
   v
Interactive Blazor connection
   |
   v
Component state changes

For applications that don't need continuous interaction, Static SSR can provide a simpler rendering model.

Why QuickGrid and Static SSR Work Well Together

A table normally needs two common interactions:

  • Sorting

  • Pagination

Both can be expressed through URLs.

For example:

/products?sort=Name&order=asc

and:

/products?page=3&sort=Name&order=asc

The URL contains enough information for the server to recreate the same table view.

This is a useful architectural pattern because the state is not hidden inside the browser.

The URL itself describes the current view.

Creating a QuickGrid

Let's create a simple product model.

public class Product
{
    public int Id { get; set; }

    public string Name { get; set; } = string.Empty;

    public string Category { get; set; } = string.Empty;

    public decimal Price { get; set; }

    public bool IsAvailable { get; set; }
}

For demonstration purposes, we can create some sample data:

private readonly List<Product> Products =
[
    new()
    {
        Id = 1,
        Name = "Laptop",
        Category = "Electronics",
        Price = 75000,
        IsAvailable = true
    },
    new()
    {
        Id = 2,
        Name = "Mechanical Keyboard",
        Category = "Accessories",
        Price = 5500,
        IsAvailable = true
    },
    new()
    {
        Id = 3,
        Name = "Monitor",
        Category = "Electronics",
        Price = 22000,
        IsAvailable = true
    },
    new()
    {
        Id = 4,
        Name = "Mouse",
        Category = "Accessories",
        Price = 1800,
        IsAvailable = true
    }
];

Now add QuickGrid to the Razor component.

@using Microsoft.AspNetCore.Components.QuickGrid

<QuickGrid Items="Products.AsQueryable()">
    <PropertyColumn Property="p => p.Id"
                    Title="ID" />

    <PropertyColumn Property="p => p.Name"
                    Title="Name"
                    Sortable="true" />

    <PropertyColumn Property="p => p.Category"
                    Title="Category"
                    Sortable="true" />

    <PropertyColumn Property="p => p.Price"
                    Title="Price"
                    Sortable="true" />

    <PropertyColumn Property="p => p.IsAvailable"
                    Title="Available" />
</QuickGrid>

The Sortable="true" property tells QuickGrid that users should be able to sort the column.

How URL-Based Sorting Works

In .NET 11, QuickGrid uses URL-based navigation for sorting and pagination by default. The sorting information can be represented using query-string parameters. (learn.microsoft.com)

Suppose the user clicks the Price column.

The URL can become:

/products?sort=Price&order=asc

Clicking the column again can change the direction:

/products?sort=Price&order=desc

The page is then rendered using that sorting state.

The flow is:

Click Price
    |
    v
URL changes
    |
    v
Server receives request
    |
    v
QuickGrid reads sorting state
    |
    v
Data is sorted
    |
    v
New HTML is rendered

This is why the feature works with Static SSR.

Why This Is Better Than a Custom Sorting Handler

A common approach in older applications is to write a custom click handler.

For example:

<button @onclick="SortByPrice">
    Price
</button>

Then:

private void SortByPrice()
{
    Products = Products
        .OrderBy(p => p.Price)
        .ToList();
}

This works well in an interactive component.

But it is tied to interactive event handling.

For a Static SSR page, URL navigation is a better fit because the sorting operation can be represented as a normal navigation.

You also get another useful benefit: the current sorting state can be bookmarked or shared.

Column Titles and Sorting

There is an important detail when using URL-based sorting.

QuickGrid uses the column title when representing sorting state in the URL. (learn.microsoft.com)

For example:

<PropertyColumn Property="p => p.Price"
                Title="Price"
                Sortable="true" />

can result in:

?sort=Price&order=asc

Now consider changing the title:

<PropertyColumn Property="p => p.Price"
                Title="Product Price"
                Sortable="true" />

The sorting identifier changes as well.

This is worth remembering if users may bookmark or share URLs.

A column title can look like simple display text, but when it participates in URL state, changing it can affect navigation behavior.

Adding Pagination

Pagination can be added using PaginationState.

@code {
    private PaginationState pagination = new()
    {
        ItemsPerPage = 10
    };
}

Then pass the state to QuickGrid:

<QuickGrid Items="Products.AsQueryable()"
           Pagination="pagination">

    <PropertyColumn Property="p => p.Id"
                    Title="ID" />

    <PropertyColumn Property="p => p.Name"
                    Title="Name"
                    Sortable="true" />

    <PropertyColumn Property="p => p.Category"
                    Title="Category"
                    Sortable="true" />

    <PropertyColumn Property="p => p.Price"
                    Title="Price"
                    Sortable="true" />

</QuickGrid>

<Paginator State="pagination" />

The page number can then be represented in the URL.

For example:

/products?page=2

When sorting is also active:

/products?page=2&sort=Price&order=desc

This gives the application a complete description of the current table state.

Why URL State Helps Users

Imagine a support engineer investigating a customer issue.

They open the product table and sort it by price. They then move to page 4.

Without URL state, they might send a screenshot to another developer.

With URL state, they can share the page URL.

For example:

/products?page=4&sort=Price&order=desc

The other developer can open the same view.

This can also be useful for:

  • Bookmarks

  • Browser refresh

  • Browser Back and Forward

  • Support tickets

  • Internal documentation

  • Reproducing a data view

Static SSR Does Not Mean "No Interactivity"

It is important not to misunderstand Static SSR.

Static SSR does not mean that the entire application can never have interactive behavior.

A Blazor application can use different rendering approaches depending on the page or component.

For example:

Product Listing
     |
     +-- Static SSR
     |
     +-- URL sorting
     |
     +-- URL pagination

while another page might use:

Order Management
     |
     +-- Interactive rendering
     |
     +-- Inline editing
     |
     +-- Live validation
     |
     +-- Real-time updates

Choose the rendering model according to the behavior the page actually needs.

Using QuickGrid With Database Data

An in-memory list is fine for learning, but production applications normally use a database.

For example:

public IQueryable<Product> GetProducts()
{
    return _dbContext.Products;
}

Then:

<QuickGrid Items="GetProducts()">
    <PropertyColumn Property="p => p.Name"
                    Title="Name"
                    Sortable="true" />

    <PropertyColumn Property="p => p.Price"
                    Title="Price"
                    Sortable="true" />
</QuickGrid>

The database query should remain queryable for as long as possible.

This allows sorting and pagination to be performed by the database rather than loading all records into application memory.

For a large table, that distinction matters.

Server-Side Data Loading

Suppose the database contains 500,000 products.

Loading everything into memory just to show 20 records is unnecessary.

A better design is:

Browser
   |
   | page=3, sort=Price
   v
Blazor
   |
   v
QuickGrid / Data Provider
   |
   v
Database
   |
   | Return required rows
   v
Blazor
   |
   v
HTML

QuickGrid's ItemsProvider model can be used when the application needs more control over data loading.

A simplified example is:

private async ValueTask<GridItemsProviderResult<Product>>
    LoadProducts(GridItemsProviderRequest<Product> request)
{
    IQueryable<Product> query =
        _dbContext.Products;

    query = request.ApplySorting(query);

    var totalCount =
        await query.CountAsync();

    var products =
        await query
            .Skip(request.StartIndex)
            .Take(request.Count ?? 10)
            .ToListAsync();

    return GridItemsProviderResult.From(
        products,
        totalCount);
}

The exact implementation depends on the application's data-access layer.

The main idea is to let the database handle the expensive work whenever possible.

Sorting Before Pagination

The order of operations matters.

This is correct:

var products = query
    .OrderBy(p => p.Price)
    .Skip(20)
    .Take(10);

The application first determines the order and then selects the requested page.

Conceptually:

All records
    |
    v
Sort
    |
    v
Skip
    |
    v
Take
    |
    v
Current page

If you paginate first and then sort only that subset, users will not get the correct globally sorted result.

Multiple QuickGrid Tables

A dashboard may contain more than one table.

For example:

Dashboard
   |
   +-- Products
   |
   +-- Customers
   |
   +-- Orders

Each table may need its own pagination and sorting state.

If all of them use the same query-string parameter names, their states can conflict.

QuickGrid provides QueryParameterNamePrefix for this situation. (learn.microsoft.com)

For example:

<QuickGrid Items="Products.AsQueryable()"
           Pagination="productPagination"
           QueryParameterNamePrefix="products">

    <PropertyColumn Property="p => p.Name"
                    Title="Name"
                    Sortable="true" />

    <PropertyColumn Property="p => p.Price"
                    Title="Price"
                    Sortable="true" />

</QuickGrid>

The product grid can then maintain its own query parameters.

Another grid can use:

QueryParameterNamePrefix="customers"

This keeps the state of the two grids separate.

Each independent grid should also have its own PaginationState.

Updating Existing CSS

Moving to URL-based QuickGrid navigation can affect custom CSS.

An existing application might have styling such as:

button.col-title {
    font-weight: 600;
}

If the sortable header is now rendered as a link, that selector may no longer apply.

You may need:

button.col-title,
a.col-title {
    font-weight: 600;
}

The same applies to pagination styles.

If your CSS was written specifically for buttons, inspect the generated HTML after upgrading.

This is a small change, but it can cause confusing UI issues if it is overlooked.

Static SSR vs Interactive QuickGrid

The choice depends on the type of application.

Requirement

Static SSR

Interactive

Display table data

Yes

Yes

URL-based sorting

Yes

Yes

URL-based pagination

Yes

Yes

Normal browser navigation

Excellent

Good

Live component updates

Limited

Excellent

Inline editing

Not ideal

Better

Real-time updates

Not ideal

Better

Simple sortable lists

Excellent

Good

Complex client-side interactions

Limited

Excellent

Static SSR is a strong option for straightforward data-list pages.

Interactive rendering is more appropriate when the table itself behaves like an application workspace.

Common Mistakes

Adding Custom JavaScript for Sorting

QuickGrid already provides sorting behavior.

Do not introduce JavaScript simply to reproduce functionality that QuickGrid already handles.

Depending on @onclick

If the requirement is simple sorting or pagination, URL-based navigation is usually a better fit for Static SSR.

Ignoring Column Titles

If the title participates in the URL sorting state, changing it can affect existing URLs.

Loading All Records

Large datasets should not be loaded completely into memory just to display one page.

Use server-side data loading where appropriate.

Forgetting Multiple Grid State

If a page has several grids, give each one a distinct query-parameter namespace.

Keeping Old CSS Unchanged

Check selectors after moving to URL-based navigation.

A selector targeting only button may no longer style a link-based sortable header.

Troubleshooting

Sorting Does Not Appear

Make sure the column is marked:

Sortable="true"

Also confirm that the underlying data source supports the required sorting operation.

URL Changes but Data Does Not

Check the data-loading pipeline.

The URL state needs to reach the grid or data provider that is responsible for loading the records.

Page State Resets

Make sure the pagination state is correctly configured and that the component is reading the current query-string state.

Sorting Works but the Header Looks Wrong

Inspect the generated HTML.

Your custom CSS may be targeting a button while QuickGrid is rendering a link.

Two Grids Change Together

Use separate QueryParameterNamePrefix values and separate pagination state objects.

Best Practices

  1. Use URL-based sorting when the table should work with Static SSR.

  2. Keep sortable column titles stable.

  3. Use PaginationState for pageable tables.

  4. Use server-side data loading for large datasets.

  5. Sort before applying Skip() and Take().

  6. Use explicit allowed sorting fields when building custom data queries.

  7. Use different query-parameter prefixes for multiple grids.

  8. Keep URL state limited to useful navigation state.

  9. Review custom CSS after upgrading QuickGrid.

  10. Use interactive rendering when the page requires richer client-side behavior.

Advantages

Using QuickGrid with URL-based sorting and Static SSR provides several practical benefits:

  • No interactive Blazor connection is required for basic sorting.

  • Sorting state can be represented in the URL.

  • Pagination can also be represented in the URL.

  • Users can share specific table views.

  • Browser navigation works naturally.

  • The server can render the requested state.

  • Less custom sorting code is required.

  • The approach works well for traditional data-list pages.

Disadvantages

There are some trade-offs:

  • Changing sorting can cause a navigation request.

  • Complex client-side interactions are not a good fit for Static SSR alone.

  • Existing CSS may need adjustment.

  • Column titles can become part of the URL state.

  • Large datasets still require careful server-side data handling.

  • Multiple grids require additional query-parameter configuration.

Summary

QuickGrid and Static SSR can work together without building a custom interactive sorting system.

The key is to treat sorting and pagination as part of the URL. A URL such as:

/products?page=2&sort=Price&order=desc

contains enough information for the application to render the requested table view again.

This approach is useful when the page mainly displays data and users need normal sorting, pagination, bookmarking, or sharing. It also avoids making a page interactive just to support basic table operations.

For production applications, the main things to watch are database-side data loading, stable column titles, multiple-grid query parameters, and CSS that may depend on the rendered HTML.

If the table needs live editing, real-time updates, or complex client-side behavior, an interactive render mode may be a better choice. For straightforward data pages, URL-based QuickGrid sorting provides a simple and maintainable solution with Static SSR.