System.InvalidOperationException: Headers are read-only, response has already started. on a statically rendered Blazor page means you set a cookie or another header after the page had already begun sending HTML. With [StreamRendering] that happens at the first await, so set the cookie before it.

Move Response.Cookies.Append above the first await in OnInitializedAsync, or remove [StreamRendering] if the page does not need to show a loading state.

The error

fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
      An unhandled exception has occurred while executing the request.
      System.InvalidOperationException: Headers are read-only, response has already started.
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpHeaders.ThrowHeadersReadOnlyException()
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpResponseHeaders.Microsoft.AspNetCore.Http.IHeaderDictionary.set_SetCookie(StringValues value)
         at Microsoft.AspNetCore.Http.ResponseCookies.Append(String key, String value)
         at FixLab.Components.Pages.Cookie.OnInitializedAsync() in ...\FixLab\Components\Pages\Cookie.razor:line 18

The path was shortened; nothing else changed. A few lines further the log adds The response has already started, the error page middleware will not be executed. The browser got a 200, a page stuck on "Loading..." and no cookie.

Why it happens

This was the page, a statically rendered component with no render mode:

@page "/cookie"
@attribute [StreamRendering]

<p>@status</p>

@code {
    [CascadingParameter] public HttpContext? HttpContext { get; set; }

    private string status = "Loading...";

    protected override async Task OnInitializedAsync()
    {
        await Task.Delay(500);
        HttpContext!.Response.Cookies.Append("theme", "dark");
        status = "Saved.";
    }
}

[StreamRendering] tells Blazor not to wait for slow initialisation. As soon as OnInitializedAsync reaches an await that has not finished, Blazor writes the page as it is now ("Loading...") to the response and keeps the connection open to stream the updated markup later. Writing the first bytes of an HTTP response sends the status line and every header with it, so from that moment the header collection is read-only. A cookie is a Set-Cookie header, and Kestrel refuses to add it. Anything that writes a header after that point fails the same way, a cookie sign-in included.

The fix

Set the cookie before the first await:

    protected override async Task OnInitializedAsync()
    {
        HttpContext!.Response.Cookies.Append("theme", "dark");
        await Task.Delay(500);
        status = "Saved.";
    }

The same request now carries the cookie, and the streamed update arrives:

HTTP/1.1 200 OK
Set-Cookie: theme=dark; path=/

The second option is to drop [StreamRendering]. A copy of the failing page without the attribute, cookie still after the await, returned 200 with Set-Cookie: theme=dark and "Saved.", because without streaming Blazor waits for OnInitializedAsync to finish before it writes anything. The price is that the visitor sees nothing until the slow work is done, which is what streaming was for.

How it was reproduced

A Blazor Web App from dotnet new blazor -n FixLab -int Server on .NET SDK 10.0.401 with the ASP.NET Core 10.0.12 runtime. The page above was added without a render mode, so it rendered statically, and Task.Delay(500) stood in for a slow database call. Running dotnet run --urls http://localhost:15480 and requesting /cookie with curl.exe -s -D - produced the log above and a response without the cookie. Moving the line above the await produced the Set-Cookie header and no errors.

Frequently asked

Why do I get headers are read-only, response has already started in Blazor?
The response had already started sending when your code tried to add a header such as a cookie. On a static page with StreamRendering, the first await in OnInitializedAsync sends the initial HTML and its headers, so set cookies before that await.
How do I set a cookie in a Blazor static SSR page?
Use the cascading HttpContext and call HttpContext.Response.Cookies.Append before anything is written to the response, which on a page with StreamRendering means before the first await in OnInitializedAsync.
Does StreamRendering stop me from setting cookies in Blazor?
Only after the first await. Streaming sends the initial render and the headers early, so cookies set before that point still go out. If the cookie depends on slow work, remove StreamRendering from that page.

More decoded errors in the Fixes category. For what runs when in a component, see The Component Lifecycle: What Runs When (and Why).