NextFs 0.1.0
See the version list below for details.
dotnet add package NextFs --version 0.1.0
NuGet\Install-Package NextFs -Version 0.1.0
<PackageReference Include="NextFs" Version="0.1.0" />
<PackageVersion Include="NextFs" Version="0.1.0" />
<PackageReference Include="NextFs" />
paket add NextFs --version 0.1.0
#r "nuget: NextFs, 0.1.0"
#:package NextFs@0.1.0
#addin nuget:?package=NextFs&version=0.1.0
#tool nuget:?package=NextFs&version=0.1.0
NextFs
NextFs is a Fable-first binding layer for building Next.js applications with F#.
Status: experimental, but already usable as a bootstrap layer for App Router projects.
What It Covers
- Next.js components:
next/link,next/image,next/script,next/form,next/head - App Router hooks and helpers from
next/navigation - Async server request APIs from
next/headersandnext/server NextRequest/NextResponsebaseline for route handlers- Inline
Directive.useServer()support for F# server actions - Wrapper generation for file-level
'use client'/'use server'entry files
The current package is aimed at Next.js 15/16 style App Router usage, including async headers() and cookies().
Repository Layout
src/NextFscontains the bindings packagesamples/NextFs.Smokecontains a compile-smoke consumer projecttools/nextfs-entry.mjsgenerates thin Next.js wrapper files with file-level directivessamples/nextfs.entries.jsonshows the wrapper manifest format
Install
dotnet add package NextFs
Runtime dependencies are expected to come from the consuming Next.js app:
nextreactreact-dom
Example
module App.Page
open Fable.Core
open Feliz
open NextFs
[<ReactComponent>]
let NavLink() =
Link.create [
Link.href "/dashboard"
prop.className "nav-link"
prop.text "Dashboard"
]
[<ExportDefault>]
let Page() =
Html.main [
prop.children [
Html.h1 "Hello from Fable + Next.js"
NavLink()
]
]
Typed object href values can be built with Href.create:
open Fable.Core.JsInterop
Link.create [
Link.hrefObject (
Href.create [
Href.pathname "/search"
Href.query (createObj [ "q" ==> "fable" ])
]
)
prop.text "Search"
]
Server-side request data is exposed as async APIs:
module App.ServerPage
open Fable.Core
open Feliz
open NextFs
[<ExportDefault>]
let Page() =
async {
let! headers = Async.AwaitPromise(Server.headers())
let userAgent = headers.get("user-agent") |> Option.defaultValue "unknown"
return
Html.pre [
prop.text userAgent
]
}
|> Async.StartAsPromise
Route handlers can use NextRequest, async route params, and NextResponse:
module App.Api.Posts
open Fable.Core
open Fable.Core.JsInterop
open NextFs
[<CompiledName("GET")>]
let get (request: NextRequest, ctx: RouteHandlerContext<{| slug: string |}>) =
async {
let! routeParams = Async.AwaitPromise ctx.``params``
return
ServerResponse.jsonWithInit
(createObj [
"slug" ==> routeParams.slug
"pathname" ==> request.nextUrl.pathname
])
(ResponseInit.create [
ResponseInit.status 200
])
}
|> Async.StartAsPromise
Inline server actions can emit 'use server' from F#:
let saveSearch (formData: obj) =
Directive.useServer()
()
File Directives
Next.js requires 'use client' and 'use server' to appear at the top of the generated JavaScript file.
Directive.useServer() covers the inline function-level case. File-level directives are different: Fable-generated ESM imports appear before emitted statements, so client/server entry modules still need a thin wrapper file.
The repository includes a wrapper generator:
node tools/nextfs-entry.mjs samples/nextfs.entries.json
Example config:
{
"entries": [
{
"directive": "use client",
"from": "./.fable/App.Page.js",
"to": "./app/page.js",
"default": true
},
{
"directive": "use server",
"from": "./.fable/App.Actions.js",
"to": "./app/actions.js",
"named": ["createPost", "deletePost"]
},
{
"from": "./.fable/App.Api.Posts.js",
"to": "./app/api/posts/route.js",
"named": ["GET", "POST"]
}
]
}
For 'use server' wrapper entries, only named exports are allowed. This matches Next.js expectations and avoids invalid export shapes.
Interop Notes
- Route handlers should be exported with JavaScript-shaped signatures:
let get (request, ctx) = ... - Multi-argument server actions should also be uncurried:
let update (prevState, formData) = ... - Use
[<CompiledName("GET")>],[<CompiledName("POST")>], and similar attributes for route handler exports
Local Validation
dotnet build NextFs.slnx -v minimal
dotnet pack src/NextFs/NextFs.fsproj -c Release -o artifacts
node tools/nextfs-entry.mjs samples/nextfs.entries.json
NuGet Publishing
The repository includes publish-nuget.yml, which publishes NextFs to nuget.org on:
- manual
workflow_dispatch - git tag pushes matching
v*
It is configured for NuGet Trusted Publishing via GitHub OIDC, not a long-lived API key.
Before the first publish, create a trusted publishing policy on nuget.org with:
- Repository Owner:
Neftedollar - Repository:
Next.fs - Workflow File:
publish-nuget.yml - Environment:
release
Roadmap
- automate wrapper generation in the consumer workflow
- expand
next/servercoverage beyond the currentNextResponsebaseline - add stronger typing for metadata, route config, and server action conventions
Contributing
Contribution workflow and commit conventions are documented in CONTRIBUTING.md.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Fable.Core (>= 4.5.0)
- Feliz (>= 3.2.0)
- FSharp.Core (>= 10.1.201)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on NextFs:
| Package | Downloads |
|---|---|
|
NextFs.Dsl
High-level F# DSL for NextFs — computation expressions and async helpers for Next.js App Router. |
GitHub repositories
This package is not used by any popular GitHub repositories.