What are segments and why are they important?
Segments in Umbraco 16 let you show different versions of the same content based on more than just language. Until now, content variation was mostly tied to cultures (like English or Danish), but segments add another layer - one that can respond to things like user context, device type, or even connection quality. Instead of duplicating pages or hacking around with custom logic, you can now use segments to cleanly manage those variations within Umbraco! It’s a small shift with big potential: the same content node, shown differently depending on the situation, variants have always been around but Umbraco 16 shows a positive trend to making them a first class citizen.
Use case: Emergency mode/low-bandwidth
Imagine this: someone tries to load your beautifully designed homepage while stuck in a lift with one bar of signal and a dying battery.
They don’t need hero videos or parallax scroll - they just need the essentials, fast. That’s where segments can come in. I created a "low-bandwidth" segment to serve a stripped-back version of the site: fewer images, no heavy scripts, just clear, critical content. It’s also handy for emergency scenarios, where speed and clarity matter more than brand polish. Same content structure, just... lighter on its feet.
Of course, this is just one use case. Segments aren’t limited to saving bandwidth or handling emergencies, they’re about showing the right content for the right context. You might have a simplified view for users with lower reading ages, or a distraction-free layout for neurodivergent audiences who benefit from reduced visual noise. The real power of segments is in how flexible they are => you define the need, and Umbraco lets you tailor the experience without duplicating everything.
Setting up segments in Umbraco
Setting up new segments in Umbraco couldn't be more simple now! The first step is to create a class that implements the interface ISegmentService
.
public class MinimalModeSegmentService : ISegmentService
{
private readonly Segment[] _segments =
[
new()
{
Alias = "low-bandwidth",
Name = "Low-Bandwidth (Stripped-Down Assets)"
}
];
public Task<Attempt<PagedModel<Segment>?, SegmentOperationStatus>> GetPagedSegmentsAsync(int skip = 0, int take = 100)
{
return Task.FromResult
(
Attempt.SucceedWithStatus<PagedModel<Segment>?, SegmentOperationStatus>
(
SegmentOperationStatus.Success,
new PagedModel<Segment>
{
Total = _segments.Length,
Items = _segments.Skip(skip).Take(take)
}
)
);
}
}
Next, we need to tell Umbraco to use our custom segment service instead of the default one.
Kenn Jacobsen has a great write-up that explains this in more detail: Umbraco 16 brings back segments. I’ve dropped the registrations into Program.cs for now, but realistically, you’ll want to move them into a IComposer to keep things clean and consistent.
builder.Services.AddUnique<ISegmentService, MinimalModeSegmentService>();
builder.Services.Configure<SegmentSettings>(settings => settings.Enabled = true);
Now that our segments are enabled, it's as simple as just enabling them for document types and then applying them to property types

Rendering the segments
Once you’ve set the segment in the variation context, Umbraco does most of the heavy lifting for you. Whether you're using classic Razor views or a headless setup, content for the current segment will be served automatically - as long as it exists.
Umbraco MVC
If you're using Umbraco's built-in view rendering, you can inject the segment logic directly into your Razor layout or view - especially for quick prototyping or small-scale scenarios.
Here’s how I handled segment detection based on the browser’s Save-Data header (with a cookie fallback), and how I set the variation context accordingly:
@{
Layout = null;
const string lowBandwidthVariation = "low-bandwidth";
bool isLowBandwidth = Context.Request.Headers.TryGetValue("Save-Data", out var saveDataHeader)
&& saveDataHeader.ToString().Equals("on", StringComparison.OrdinalIgnoreCase);
if (!isLowBandwidth
&& Context.Request.Cookies.TryGetValue("SiteMode", out var siteModeCookie)
&& siteModeCookie == lowBandwidthVariation)
{
isLowBandwidth = true;
}
if (isLowBandwidth)
{
VariationContextAccessor.VariationContext =
new VariationContext(VariationContextAccessor.VariationContext?.Culture, lowBandwidthVariation);
}
}
<main>
<span style="display:none">@isLowBandwidth</span>
<h1 class="heading">@Model.Value("heroHeading")</h1>
<p>@Model.Value("importantCopy")</p>
</main>
🛠️ Note: While this does work, it's not great to embed this kind of logic in your view templates. For production, it’s better to move this into a middleware or a controller to keep your rendering layer clean and focused purely on display. Razor should show content, not make decisions about segment context
Headless
I’ll admit it - I love Vue. It’s been my go-to for frontends for years, and it pairs beautifully with Umbraco when running headless. In this case, I used a Vue 3 app (with a little help from @vueuse/core
) to detect network conditions and request the right content segment dynamically.
If the user is on a slow connection, or has data-saving enabled, we send the Accept-Segment: low-bandwidth header to the Umbraco Delivery API. That’s it. Umbraco handles the rest returning the appropriate version of the content based on the segment.
Here's how that looks:
<script setup lang="ts">
import {useFetch} from '@vueuse/core'
import {computed} from 'vue'
import type {HomeContent} from "./types.ts";
import {useNetwork} from '@vueuse/core'
const {isOnline, downlink, effectiveType, saveData} = useNetwork()
const shouldUseLowBandwidth = computed(() => {
return !isOnline.value || effectiveType.value === 'slow-2g' || effectiveType.value === '2g' || saveData.value || downlink.value < 0.5
})
# This should be stored somewhere better - in an .env file
const umbracoUrl = 'https://localhost:44320'
const url = `${umbracoUrl}/umbraco/delivery/api/v2/content/item/home?fields=properties%5B%24all%5D`
/// if low bandwidth, send the 'Accept-Segment': low-bandwidth
const headers = shouldUseLowBandwidth.value
? {
'Accept-Segment': 'low-bandwidth'
}
: {}
const {isFetching, error, data} = useFetch<HomeContent>(url, headers).json()
// Function to safely access nested properties
function safeGet<T, K extends keyof T>(obj: T | undefined, key: K): T[K] | undefined {
return obj ? obj[key] : undefined
}
function getHeadingText(): string {
if (!data.value) return 'Default Heading'
return safeGet(data.value, 'properties')?.heroHeading || 'Default Heading'
}
function getMarkup(): string {
if (!data.value) return ''
const properties = safeGet(data.value, 'properties')
if (!properties) return ''
return safeGet(properties.importantCopy, 'markup') || ''
}
</script>
<template>
<div v-if="isFetching">Loading...</div>
<div v-if="error">Error: {{ error }}</div>
<div v-if="!isFetching && !error" class="home-content">
<h1>{{ getHeadingText() }}</h1>
<div v-html="getMarkup()" class="important-copy"></div>
<div class="meta-info" v-if="data && data.value">
<p>Content Type: {{ safeGet(data.value, 'contentType') || 'Unknown' }}</p>
<p>Last Updated: {{ data.value?.updateDate ? new Date(data.value.updateDate).toLocaleString() : 'Unknown' }}</p>
</div>
</div>
</template>
How it all looks

Conclusion
Segments aren’t new to Umbraco, but in version 16, they’ve finally stepped into the spotlight. ✨ What used to be a hidden, behind-the-scenes feature is now fully editable, previewable, and genuinely useful, not just for marketing teams running A/B tests, but for developers building smart, flexible experiences.
You could use segments for accessibility modes, regulatory views, device-specific content, or anything else your users might need - all without duplicating nodes or making your CMS a tangled spaghetti mess. The low-bandwidth demo here is just one example.
The direction is clear: Umbraco is giving us serious power to build content that adapts.
One node. Multiple dimensions. Real-world context, built in.