by Joche Ojeda | Oct 7, 2025 | Oqtane
Oqtane Notes: Understanding Site Settings vs. App Settings
OK — it’s time for another blog post (or maybe just a mental note) about Oqtane.
I’ve been doing what feels like a million installations of it lately. Honestly, if the Oqtane team gets a notification every time I spin up a new instance, they’re probably tired of seeing my name by now. I’ve been spending nearly every free minute exploring the framework — I love diving into new technologies, digging into the source code, and figuring out how things really work.
One of the most beautiful parts about Oqtane is that it’s open source. You can simply go into the repository and inspect the source code yourself. Some parts might not be obvious at first glance, but the project’s creator, Shaun Walker, is incredibly responsive and helpful to the community. I think I’ve only posted a couple of issues over the years, but every single time I’ve woken up the next morning with a thoughtful response waiting — even though I’m usually several time zones ahead in Europe. He really knows Oqtane inside and out.
Hosting Models and Render Modes
As you probably know, one of Oqtane’s biggest strengths is its flexibility with Blazor hosting models. It can run as Server or WebAssembly, and you can switch between them with a simple configuration change.
On top of that, Oqtane supports different render modes for components: Interactive
or Static
. In simple terms, you can choose to render content on the server (similar to how PHP works) or make it fully interactive like a standard Blazor app where the state refreshes dynamically.
You can toggle these behaviors with just a few clicks in the admin backend — which is awesome once you understand how the settings are actually applied.
My Confusion (and the Lesson Learned)
This post was originally meant to be a follow-up to the previous one about database configuration, but I ran into an interesting issue while testing API controllers. I wanted to confirm that when I ran the application in WebAssembly mode, it would hit the API controllers correctly.
It didn’t — at least not at first.
I spent quite a while trying to figure out why. Oqtane has both app-level settings (in appsettings.json
) and site-level settings (in the admin panel), and it wasn’t immediately clear which ones took priority. I initially thought I could just change the render and runtime options in appsettings.json
, restart the app, and see the effect. But it didn’t work that way.
After some trial and error — and a helpful reply from Shaun — I realized my mistake. When you first spin up a new site, Oqtane uses the values defined in appsettings.json
. But once that site exists, it maintains its own configuration separately. From that point forward, any runtime or render mode changes must be made in the site settings from the admin panel, not in the original configuration file.
Server Runtime


WebAssembly Runtime

The Takeaway
If you edit appsettings.json
after your first site is already created, it won’t affect the existing site — those values only apply when a new site is initialized.
So, to summarize:
- Before the first run → Configure defaults in
appsettings.json
.
- After the site is running → Change settings from the admin backend.
That was the source of my confusion. Hopefully, this note saves someone else a few hours of head-scratching.
Thanks again to Shaun and the entire Oqtane team for keeping this project alive and so well supported.
These posts are just my personal notes, but I hope they help someone who’s following the same learning path.
by Joche Ojeda | Dec 1, 2024 | Blazor
My journey with Microsoft Semantic Kernel marked the beginning of a new adventure: stepping out of my comfort zone as a backend developer to create applications with user interfaces, rather than just building apps for unit and integration testing.
I naturally chose Blazor as my UI framework, and I’ll be sharing my frontend development experiences here. Sometimes it can be frustratingly difficult to accomplish seemingly simple tasks (like centering a div!), but AI assistants like GitHub Copilot have been incredibly helpful in reducing those pain points.
One of my recent challenges involved programmatically including JavaScript and CSS in Blazor applications. I prefer an automated approach rather than manually adding tags to HTML. Back in the .NET 5 era, I wrote an article about using tag helpers for this purpose, which you can find here
However, I recently discovered that my original approach no longer works. I’ve been developing several prototypes using the new DevExpress Chat component, and many of these prototypes include custom components that require JavaScript and CSS. Despite my attempts, I couldn’t get these components to work with the tag helpers, and the reason wasn’t immediately obvious. During the Thanksgiving break, I decided to investigate this issue, and I’d like to share what I found.
With the release of .NET 8, Blazor introduced a new web app template that unifies Blazor Server and WebAssembly into a single project structure. This change affects how we inject content into the document’s head section, particularly when working with Tag Helpers or components.
Understanding the Changes
In previous versions of Blazor, we typically worked with _Host.cshtml
for server-side rendering, where traditional ASP.NET Core Tag Helpers could target the <head>
element directly. The new .NET 8 Blazor Web App template uses App.razor
as the root component and introduces the <HeadOutlet>
component for managing head content.
Approach 1: Adapting Tag Helpers
If you’re migrating existing Tag Helpers or creating new ones for head content injection, you’ll need to modify them to target HeadOutlet instead of the head element:
using Microsoft.AspNetCore.Razor.TagHelpers;
namespace YourNamespace
{
[HtmlTargetElement("HeadOutlet")]
public class CustomScriptTagHelper : TagHelper
{
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.PostContent.AppendHtml(
"<script src=\"_content/YourLibrary/js/script.js\"></script>"
);
}
}
}
Remember to register your Tag Helper in _Imports.razor
:
@addTagHelper *, YourLibrary
Approach 2: Using Blazor Components (Recommended)
While adapting Tag Helpers works, Blazor offers a more idiomatic approach using components and the HeadContent component. This approach aligns better with Blazor’s component-based architecture:
@namespace YourNamespace
@implements IComponentRenderMode
<HeadContent>
<script src="_content/YourLibrary/js/script.js"></script>
</HeadContent>
To use this component in your App.razor
:
<head>
<!-- Other head elements -->
<HeadOutlet @rendermode="RenderModeForPage" />
<YourScriptComponent @rendermode="RenderModeForPage" />
</head>
Benefits of the Component Approach
- Better Integration: Components work seamlessly with Blazor’s rendering model
- Render Mode Support: Easy to control rendering based on the current render mode (Interactive Server, WebAssembly, or Auto)
- Dynamic Content: Can leverage Blazor’s full component lifecycle and state management
- Type Safety: Provides compile-time checking and better tooling support
Best Practices
- Prefer the component-based approach for new development
- Use Tag Helpers only when migrating existing code or when you need specific ASP.NET Core pipeline integration
- Always specify the
@rendermode
attribute to ensure proper rendering in different scenarios
- Place custom head content components after HeadOutlet to ensure proper ordering
Conclusion
While both approaches work in .NET 8 Blazor Web Apps, the component-based approach using HeadContent provides a more natural fit with Blazor’s architecture and offers better maintainability and flexibility. When building new applications, consider using components unless you have a specific need for Tag Helper functionality.