docs: Add inertia community adapter
This PR adds documentation for a community adapter for Inertia.js. Inspired by the conversations on X. Related #786
@Joehoel is attempting to deploy a commit to the 47ng Team on Vercel.
A member of the Team first needs to authorize it.
Also might be related: https://github.com/inertiajs/inertia/issues/2110
While creating the adapter I was facing some issues with persistent layouts from inertia and the state not updating correctly. Anyone face similar issues?
Echoing my https://github.com/inertiajs/inertia/issues/2110#issuecomment-2506715836, the key thing for adapters to work is that the searchParams value returned be reactive to URL changes, no matter where they originate from (nuqs itself, Link components, imperative router calls, calls to history.replaceState by 3rd party code etc).
This is unfortunately not an easy thing to do, and it looks like most adapters (apart from Next.js which does some of that for us) need to do the same kind of heavy lifting work to get this reactive value.
The latest updates on your projects. Learn more about Vercel for GitHub.
| Project | Deployment | Preview | Comments | Updated (UTC) |
|---|---|---|---|---|
| nuqs | Preview | Comment | Aug 19, 2025 2:22pm |
Thanks for clarifying. I can look into it when I have some extra time but if someone else wants to have a stab!
Maybe we can take some inspiration from the remix/react-router code for the reactive search params?
I have some updates for the Remix adapter that I need to test & push, it's adding a lot of those things I mentioned but it could probably be refactored to benefit a lot of other adapters. I might have more time to work on that next week.
Just realised Inertia has a usePage hook.
import { router, usePage } from "@inertiajs/react";
import {
type unstable_AdapterOptions as AdapterOptions,
unstable_createAdapterProvider as createAdapterProvider,
renderQueryString,
} from "nuqs/adapters/custom";
function useNuqsInertiaAdapter() {
const { searchParams } = new URL(location.origin + usePage().url);
function updateUrl(search: URLSearchParams, options: AdapterOptions) {
const url = new URL(location.href);
url.search = renderQueryString(search);
router.visit(url, {
replace: options.history === "replace",
preserveScroll: !options.scroll,
preserveState: options.shallow,
});
}
return {
searchParams,
updateUrl,
};
}
export const NuqsAdapter = createAdapterProvider(useNuqsInertiaAdapter);
Implemented this is my test app and it works and fixes this issue https://github.com/inertiajs/inertia/issues/2110#issuecomment-2506715836
Looks good! However I think the shallow option doesn't really map to Inertia's state preservation mechanism.
In nuqs, shallow: true (the default) means we should only update the client-side application when a search param changes (using the history API), and the server is not called. Setting shallow: false opts-in to calling the server for search params that do need to be read server-side.
The history API part is also something that could be factored out of all of those adapters.
@Joehoel will this PR be merged?
@thewebartisan7 considering this is a documentation-only update, have you tried the suggested code? My concern was that it doesn't satisfy the shallow option, which should:
- Update search params client-side only by default
- Notify the server (whatever that means in this case) when setting
shallow: false
@thewebartisan7 considering this is a documentation-only update, have you tried the suggested code? My concern was that it doesn't satisfy the
shallowoption, which should:
- Update search params client-side only by default
- Notify the server (whatever that means in this case) when setting
shallow: false
I did test it, and it seems to work fine for me using the version @Joehoel suggested with usePage().url:
const { searchParams } = new URL(location.origin + usePage().url);
I also tested it using just router.reload(), which reloads the current URL, and that also appears to work well. Here's an example:
import { router, usePage } from "@inertiajs/react";
import {
unstable_createAdapterProvider as createAdapterProvider,
} from "nuqs/adapters/custom";
function useNuqsInertiaAdapter() {
const url = usePage().url
const { searchParams } = new URL(location.origin + url);
function updateUrl() {
router.reload()
}
return {
searchParams,
updateUrl,
};
}
export const NuqsAdapter = createAdapterProvider(useNuqsInertiaAdapter);
I tested this in a repo that uses a persistent layout, which seems to be where the issue arises: https://github.com/thewebartisan7/inertia-nuqs/blob/main/resources/js/layouts/layout.tsx
I don’t have much experience with nuqs, but based on my testing, it seems to work as expected. The search params appear to stay in sync with the Inertia state. Both router.reload() and router.visit() seem to do the trick.
One thing different from @Joehoel example is that I wrap top level App component around NuqsAdapter, see:
setup({ el, App, props }) {
const root = createRoot(el);
root.render(<NuqsAdapter><App {...props} /></NuqsAdapter>);
},
But it's not clear here https://github.com/inertiajs/inertia/issues/2110#issue-2703042228 how he created the persistent layout since according to Inertia docs it must be set in resolve().
Can anyone confirm if the above code works and enabled Nuqs to work within Inertia. We are migrating from mixed stack which uses Nextjs to using Inertia and would love to keep using nuqs as it is such a powerful tool in React.
Can anyone confirm if the above code works and enabled Nuqs to work within Inertia. We are migrating from mixed stack which uses Nextjs to using Inertia and would love to keep using nuqs as it is such a powerful tool in React.
For everyone looking for a working version, this is the one:
Fully support shallow option.
import * as React from "react"
import { router, usePage } from "@inertiajs/react"
import {
unstable_createAdapterProvider as createAdapterProvider,
renderQueryString,
type unstable_AdapterInterface as AdapterInterface,
type unstable_AdapterOptions as AdapterOptions,
type unstable_UpdateUrlFunction as UpdateUrlFunction,
} from "nuqs/adapters/custom"
function useNuqsInertiaAdapter(): AdapterInterface {
const currentUrl = usePage().url
const searchParams = React.useMemo(
() => new URL(`${location.origin}${currentUrl}`).searchParams,
[currentUrl]
)
const updateUrl: UpdateUrlFunction = React.useCallback(
(search: URLSearchParams, options: AdapterOptions) => {
const url = new URL(window.location.href)
url.search = renderQueryString(search)
// Server-side request
if (options?.shallow === false) {
router.visit(url, {
replace: options.history === "replace",
preserveScroll: !options.scroll,
preserveState: true,
async: true,
})
return
}
const method = options.history === "replace" ? "replace" : "push"
router[method]({
url: url.toString(),
clearHistory: false,
encryptHistory: false,
preserveScroll: !options.scroll,
preserveState: true,
})
},
[]
)
return {
searchParams,
updateUrl,
}
}
export const NuqsAdapter = createAdapterProvider(useNuqsInertiaAdapter)
Thanks @anhskohbo, I created a fork of the Inertia Ping CRM demo app and added your adapter on top, it just needed an extra step to make the search params optimistic (to avoid a flash when updating them, as the nuqs state was ahead of the URL), but it works great!
I also updated that PR to read the adapter source from that fork, so we can open PRs there when things need to evolve, and the docs will follow. 🙌
:tada: This PR is included in version 2.5.0-beta.7 :tada:
The release is available on:
Your semantic-release bot :package::rocket:
:tada: This PR is included in version 2.5.0 :tada:
The release is available on:
Your semantic-release bot :package::rocket: