Starter App Walkthrough
This page is the shortest explanation of how the examples/nextfs-starter app is meant to work.
Goal
The starter is a reference App Router project where:
- handwritten F# lives under
src/** - Fable emits JavaScript into
.fable/** nextfs.entries.jsonmaps compiled modules to Next.js entry files- generated wrappers live under
app/**plus the rootproxy.jsand instrumentation entries
It is not a separate framework. The goal is to keep the filesystem shape recognizable to a Next.js developer while letting the implementation live in F#.
File Ownership
Edit by hand:
src/**next.config.mjs
Generated files (do not edit directly):
nextfs.entries.json— regenerated from[<NextFs.NextFsEntry>]attributes bynpm run scan.fable/**— emitted by Fableapp/**,proxy.js,instrumentation.js,instrumentation-client.js— emitted bygen:wrappers
If a generated file looks wrong, fix the F# source or its [<NextFsEntry>] annotation and re-run npm run sync:app. Do not patch generated files directly.
Command Loop
From the repository root:
dotnet tool restore
From examples/nextfs-starter:
npm install
npm run sync:app
npm run dev
For iterative work:
npm run watch:fable
npm run dev
Command meaning:
build:fsharp: compile-only .NET check for the F# projectbuild:fable: emit.fable/**JavaScript from F#scan: regeneratenextfs.entries.jsonfrom[<NextFs.NextFsEntry>]attributes in the.fsprojgen:wrappers: rewriteapp/**and root wrapper files fromnextfs.entries.jsonsync:app: runbuild:fable, thenscan, thengen:wrappers
Mapping Example
Typical flow:
src/App/Page.fscarries a[<NextFs.NextFsEntry("app/page.js", ...)>]attributenpm run scanreads the.fsproj, finds the attribute, and writesnextfs.entries.jsonnpm run build:fablecompilessrc/App/Page.fsinto.fable/App/Page.jstools/nextfs-entry.mjsreadsnextfs.entries.jsonand writes a wrapper like:
'use client'
// Generated by NextFs. Do not edit by hand.
export { default } from '../.fable/App/Page.js'
Next.js consumes app/page.js, but the implementation logic still lives in F#.
Why The Wrapper Layer Exists
Next.js needs file-level directives such as 'use client' and 'use server' to appear before imports.
Fable-generated JavaScript starts with imports, so App Router entry modules often need a separate wrapper file even when the implementation itself is already correct.
That is why the starter keeps two generated layers:
.fable/**for the compiled implementationapp/**for the Next.js-facing entrypoints
Current Upstream Note
As of March 21, 2026, some environments still hit an upstream Fable CLI hang during dotnet fable runs. The current tracker is fable-compiler/Fable#4326.
As of March 21, 2026, the starter is validated end-to-end with a real:
npm run sync:app
npm run build
The starter therefore serves as both:
- a real folder-layout reference for Next.js + F#
- a checked-in, CI-validated example that completes a real
next build