{
  "schema": "https://ai-atoms.com/schemas/skill-v1.json",
  "type": "skill",
  "id": "skill/support-prerendering",
  "version": "1.0.0",
  "name": "support-prerendering",
  "description": "Make interactive Blazor components work correctly with prerendering. USE FOR fixing duplicate data loads, UI flicker during prerender-to-interactive handoff, null references during prerender, persisting state across prerender, disabling prerendering, excluding pages from interactive routing, or detecting whether a component is currently prerendering. DO NOT USE for choosing which render mode to use (see create-blazor-project) or general component authoring (see author-component).",
  "system_prompt_fragment": "# Support Prerendering\n\n## How Prerendering Works\n\nPrerendering is **on by default** for all interactive render modes. The server renders the component as static HTML and ships it to the browser immediately. Then the interactive runtime (Server/WebAssembly) loads and re-renders the component with full interactivity.\n\nThis means:\n- `OnInitializedAsync` runs **twice** — once during prerender (static), once when the interactive runtime attaches.\n- `OnAfterRenderAsync` is **NOT** called during prerender — only after the interactive render.\n- Internal navigation between interactive pages (interactive routing) **skips prerendering** — prerendering only happens on full page loads.\n\n## Step 1 — Read the Project's AGENTS.md\n\nCheck the project's `AGENTS.md` for the **Interactivity Mode** and **Interactivity Scope**:\n\n| Mode | Prerendering applies? |\n|------|----------------------|\n| None (Static SSR) | No — there's no interactive handoff |\n| Server | Yes |\n| WebAssembly | Yes |\n| Auto | Yes |\n\nIf the mode is `None`, this skill doesn't apply.\n\n## Persist State Across Prerender → Interactive\n\nThe most common prerendering problem: data loaded in `OnInitializedAsync` during prerender is thrown away and re-fetched when the interactive runtime attaches. This causes flicker and duplicate API/DB calls.\n\n### Recommended: `[PersistentState]` attribute\n\nAnnotate properties to automatically serialize during prerender and restore on interactive activation:\n\n```razor\n@page \"/forecasts\"\n@rendermode InteractiveServer\n\n<h1>Weather</h1>\n\n@if (Forecasts is null)\n{\n    <p>Loading...</p>\n}\nelse\n{\n    @foreach (var f in Forecasts)\n    {\n        <p>@f.Date: @f.TemperatureC°C</p>\n    }\n}\n\n@code {\n    [PersistentState]\n    public WeatherForecast[]? Forecasts { get; set; }\n\n    protected override async Task OnInitializedAsync()\n    {\n        Forecasts ??= await ForecastService.GetForecastsAsync();\n    }\n}\n```\n\nThe `??=` pattern is critical — it means \"only fetch if the property wasn't already restored from prerender state.\"\n\n### Multiple instances of the same component\n\nWhen the same component type appears multiple times, use `@key` to disambiguate state:\n\n```razor\n@foreach (var item in items)\n{\n    <ItemCard @key=\"item.Id\" />\n}\n```\n\n### Advanced: `PersistentComponentState` service\n\nFor complex scenarios (dynamic keys, custom serialization), use the imperative API:\n\n```csharp\n@inject PersistentComponentState ApplicationState\n\n@code {\n    private List<Order>? orders;\n\n    protected override async Task OnInitializedAsync()\n    {\n        ApplicationState.RegisterOnPersisting(PersistOrders);\n\n        if (!ApplicationState.TryTakeFromJson<List<Order>>(\"orders\", out var restored))\n        {\n            orders = await OrderService.GetOrdersAsync();\n        }\n        else\n        {\n            orders = restored;\n        }\n    }\n\n    private Task PersistOrders()\n    {\n        ApplicationState.PersistAsJson(\"orders\", orders);\n        return Task.CompletedTask;\n    }\n}\n```\n\n## Disable Prerendering\n\nDisable prerendering when a component depends on browser APIs immediately or when the prerender+interactive double render causes problems you can't solve with `[PersistentState]`.\n\n### On a component definition\n\n```razor\n@rendermode @(new InteractiveServerRenderMode(prerender: false))\n```\n\nReplace `InteractiveServerRenderMode` with `InteractiveWebAssemblyRenderMode` or `InteractiveAutoRenderMode` as needed.\n\n### On a component instance\n\n```razor\n<MyChart @rendermode=\"new InteractiveServerRenderMode(prerender: false)\" />\n```\n\n### On the entire app\n\nIn `App.razor`:\n\n```razor\n<HeadOutlet @rendermode=\"new InteractiveServerRenderMode(prerender: false)\" />\n<Routes @rendermode=\"new InteractiveServerRenderMode(prerender: false)\" />\n```\n\nNote: A parent's prerendering setting overrides children. If `<Routes>` disables prerendering, individual pages cannot re-enable it.\n\n## Exclude Pages from Interactive Routing\n\nIn a globally interactive app, some pages may need `HttpContext` (cookies, request headers, response status codes). These pages must render via static SSR, not inside the interactive runtime.\n\nUse `[ExcludeFromInteractiveRouting]`:\n\n```razor\n@page \"/privacy\"\n@attribute [ExcludeFromInteractiveRouting]\n\n<h1>Privacy Policy</h1>\n```\n\nThis forces a **full page reload** when navigating to this page, exiting interactive routing. The page renders as static SSR with full `HttpContext` access.\n\nIn `App.razor`, conditionally apply the render mode:\n\n```razor\n<!DOCTYPE html>\n<html>\n<head>\n    <HeadOutlet @rendermode=\"RenderModeForPage\" />\n</head>\n<body>\n    <Routes @rendermode=\"RenderModeForPage\" />\n    <script src=\"_framework/blazor.web.js\"></script>\n</body>\n</html>\n\n@code {\n    [CascadingParameter]\n    public HttpContext HttpContext { get; set; } = default!;\n\n    private IComponentRenderMode? RenderModeForPage =>\n        HttpContext.AcceptsInteractiveRouting() ? InteractiveServer : null;\n}\n```\n\nReplace `InteractiveServer` with the app's configured render mode.\n\n## Detect Prerender vs Interactive at Runtime\n\nUse `RendererInfo` to guard code that should only run interactively:\n\n```csharp\nprotected override async Task OnInitializedAsync()\n{\n    if (RendererInfo.IsInteractive)\n    {\n        // Only runs during the interactive render, not during prerender\n        await StartSignalRConnection();\n    }\n}\n```\n\n`RendererInfo` properties:\n- `IsInteractive` — `false` during prerender, `true` after interactive runtime attaches\n- `Name` — `\"Static\"` during prerender, `\"Server\"` or `\"WebAssembly\"` when interactive\n\n## Client Services Fail During Prerender\n\nComponents in the `.Client` project prerender on the server. Services registered only in the client `Program.cs` (e.g., `IWebAssemblyHostEnvironment`) won't be available during prerender.\n\nFix by one of:\n1. **Register a matching service on the server** — both `Program.cs` files provide the service\n2. **Make the service optional** — use constructor injection with a nullable default: `public MyComponent(IMyService? svc = null)`\n3. **Create a service abstraction** — interface in `.Client`, implementations in both projects\n4. **Disable prerendering** for that component\n\n## Don'ts\n\n- Don't call JS interop in `OnInitializedAsync` — JS isn't available during prerender. Use `OnAfterRenderAsync(firstRender)`.\n- Don't assume `OnInitializedAsync` runs once — it runs twice with prerendering. Always use `[PersistentState]` or `??=` guards.\n- Don't use `HttpContext` in interactive components — it's only available during the static prerender, not during the interactive lifetime. Use `[ExcludeFromInteractiveRouting]` for pages that need it.\n- Don't disable prerendering as a first resort — it hurts perceived load time and SEO. Use `[PersistentState]` to preserve state instead.",
  "applicable_domains": [
    "code",
    "dotnet",
    "engineering"
  ],
  "invocation": [
    "/support-prerendering"
  ],
  "tags": [
    "dotnet-blazor",
    "dotnet",
    "csharp",
    "microsoft"
  ],
  "authored_by": "anthropics",
  "source_url": "https://github.com/dotnet/skills/blob/main/plugins/dotnet-blazor/skills/support-prerendering/SKILL.md",
  "lifecycle": "stable",
  "category": "dotnet",
  "provenance": {
    "source": "dotnet/skills",
    "source_url": "https://github.com/dotnet/skills/blob/main/plugins/dotnet-blazor/skills/support-prerendering/SKILL.md",
    "author": "Microsoft / .NET Foundation",
    "license": "MIT",
    "notes": "Imported by scripts/import-anthropic-skills.py."
  }
}