Native AOT

Publish a Native AOT Inertia.Net app with source-generated JSON metadata.

Inertia.Net and Inertia.Net.FastEndpoints are annotated IsAotCompatible. A real app is published with Native AOT in CI (tests/Inertia.Net.AotSmoke) with zero trim and AOT warnings allowed. The library never reflects over your props: values are written with JsonTypeInfo from your JsonSerializerContext.

1. Register a JSON context

Register every prop type and chain the context into the HTTP JSON options:

Text
builder.Services.ConfigureHttpJsonOptions(o =>
    o.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonContext.Default));

[JsonSerializable(typeof(UsersPage))]               // typed page props
[JsonSerializable(typeof(UserDto))]
[JsonSerializable(typeof(List<UserDto>))]           // closed collection types you return
[JsonSerializable(typeof(UserDto[]))]
[JsonSerializable(typeof(Stats))]                   // the T of Defer(() => new Stats(...)) and friends
internal sealed partial class AppJsonContext : JsonSerializerContext;

Register the type that each loader returns and each plain value you place in InertiaProps, plus flash values that are not plain strings or numbers. The library's own context already covers string, bool, int, long, double, decimal, float, Guid, DateTime, DateTimeOffset, DateOnly, TimeOnly, TimeSpan, JsonElement, string[], int[], List<string>, Dictionary<string, string>, Dictionary<string, string[]>, InertiaProps and Dictionary<string, object>.

Members of type InertiaProp are accepted by the source generator without any attribute.

2. Make validation models public

The .NET 10 AddValidation() source generator skips internal types, and validation then silently does nothing. Only one AddValidation() call site per project is allowed.

3. Add ProblemDetails

Text
builder.Services.AddProblemDetails();

The non-Inertia 400 validation problem needs the ProblemDetails JSON metadata, or it cannot be serialized without reflection.

4. Avoid anonymous types

Anonymous types do not work as props or flash values under AOT. Use InertiaProps, records or classes registered in the context.

5. Copy content files

app.html and the Vite manifest are read from disk at runtime. Copy them to the publish directory, and set the content root to the binary's directory when it may run from elsewhere:

Text
var builder = WebApplication.CreateSlimBuilder(new WebApplicationOptions
{
    Args = args,
    ContentRootPath = AppContext.BaseDirectory,
});
Text
<PropertyGroup>
  <PublishAot>true</PublishAot>
  <JsonSerializerIsReflectionEnabledByDefault>false</JsonSerializerIsReflectionEnabledByDefault>
</PropertyGroup>
<ItemGroup>
  <Content Include="app.html" CopyToPublishDirectory="PreserveNewest" />
</ItemGroup>

6. Use Minimal APIs or FastEndpoints

MVC is not AOT compatible (neither is MVC itself), so Inertia.Net.Mvc is not either. Use Minimal APIs or FastEndpoints.

7. FastEndpoints source generation

Text
builder.Services.AddFastEndpoints(new[] { DiscoveredTypes.All });

DiscoveredTypes is generated by the FastEndpoints.Generator package, in the namespace named after the assembly. See samples/FastEndpoints.Svelte.

8. Trim HTTP/3

The SSR client never uses HTTP/3, so it can be trimmed out:

Text
<ItemGroup>
  <RuntimeHostConfigurationOption Include="System.Net.SocketsHttpHandler.Http3Support" Value="false" Trim="true" />
</ItemGroup>

Cached props, the SSR cache, nonces, prefetching, MapInertia and the DevTools recorder are all AOT safe and covered by the smoke test.

Catch missing registrations early

Set JsonSerializerIsReflectionEnabledByDefault=false in regular builds too. A missing JsonSerializable registration then fails in tests instead of only in the AOT publish.

The reference app is tests/Inertia.Net.AotSmoke/Program.cs. Run it end to end with bash eng/aot-smoke.sh on Linux with clang and zlib installed.