srccontains TypeScript source files;libwill contain compiled JavaScript, which is what should be imported by the external application.- Import paths used by the external application need to specify the full path starting from the package name,
in the form
@databiosphere/findable-ui/lib/<path>, where<path>is the path of the file within thelibfolder.
This section is the checklist for upgrading a findable-ui consumer from Next 15 to Next 16. The detailed wiring is in Consumer setup for Next.js 16 below; this section tells you everything that needs to change in a consumer repo.
Note on findable-ui's peer-dep range: findable-ui's
nextpeer dep is pinned to^16(not^15.5.13 || ^16). There is no v15 fallback — consumers must move to Next 16 to take the matching findable-ui release.
next→^16eslint-config-next→^16@next/eslint-plugin-next→^16next-mdx-remote→^6(non-optional peer dep of findable-ui — must be bumped even if your consumer doesn't render MDX directly, or you'll get peer-dep warnings and findable-ui's MDX helpers will break)@databiosphere/findable-ui→ the release that ships the^16peer dep- Install MUI's Emotion helpers (new):
npm install @mui/material-nextjs @emotion/server
Set the project's Node version to 22.13.0 (update .nvmrc, CI workflows, and any Dockerfile FROM node:... lines).
Required to avoid React hydration warnings on MUI-styled components. Update pages/_app.tsx with AppCacheProvider and pages/_document.tsx with DocumentHeadTags + documentGetInitialProps — exact snippets in Consumer setup for Next.js 16.
Change next dev and next build invocations in package.json scripts to next dev --webpack / next build --webpack. Turbopack + Pages Router + MUI is broken upstream — see the Turbopack opt-out note for the tracking issue.
If your consumer calls serialize() directly (i.e. not through findable-ui's buildStaticProps helper), pass blockJS: false. See Note on next-mdx-remote@6 for why.
Next 16 removes next lint and the v16 config packages target flat config. Replace .eslintrc.json with eslint.config.mjs; findable-ui's own eslint.config.mjs is a reference implementation. Replace any next lint invocations in CI with npx eslint . (or equivalent).
Pages Router on Next 16 still fully supports next/router. Consumers do not need to migrate their own next/router call sites. findable-ui itself keeps next/router in the call sites where next/navigation wasn't a clean swap; this is intentional and supported.
next build --webpacksucceeds.- Browser console shows no React hydration warnings on findable-ui-rendered pages.
- MDX-rendered pages that pass JSX expression attributes (e.g.
<Breadcrumbs breadcrumbs={[...]} />) still receive their props. - Unit + Playwright e2e suites pass.
findable-ui's CHANGELOG is generated by release-please from Conventional Commits. The Next 16 peer-dep bump lands in the release whose CHANGELOG entry includes chore!: next.js 16 prep: pin 'next' peer dep to ^16; pin consumers to that version or later.
Consuming apps on Next.js 16 must wire up MUI's Emotion cache helpers in _app.tsx and _document.tsx. Without this wiring, the browser will report React hydration warnings on components styled by MUI + Emotion (which is most of findable-ui).
This setup involves a package called @mui/material-nextjs and a helper called documentGetInitialProps. The naming uses "SSR" / "document" terminology because the package targets Next.js generally, including deployments that do run a runtime HTTP server. It still applies to static-export deployments. All the work described below runs at next build time, during the same step that generates the static .html files. There is no runtime server involved.
When the docs say "extract styles on the server," read it as "extract styles during the static HTML build step."
- findable-ui components are styled with MUI + Emotion (CSS-in-JS).
- Emotion generates
css-XXXXXXXclass names by hashing styles in the order they are first encountered during the React render tree walk. - Next.js builds the static HTML by running the React tree once during
next build, then the browser runs the same tree again during hydration. For hydration to succeed, both passes must produce the same class names. - Under Next 16 (Turbopack is the default bundler), the module evaluation order during the build can diverge from the order in the browser, so the same component can be assigned a different hash on each pass. React 19 reports this as a hydration mismatch.
- The fix: capture Emotion's style cache during the build's HTML generation step and inject the resulting
<style>tags into the static HTML. The browser then hydrates against the exact class names the build wrote.
Install in the consuming project:
npm install @mui/material-nextjs @emotion/server@mui/material-nextjs— MUI's official integration package; provides the cache provider and the document-head extractor@emotion/server— peer dependency of the document helper; used during the build to flush Emotion styles into the HTML
Next 16 makes Turbopack the default bundler. Turbopack + Pages Router + MUI is currently broken — Emotion class names diverge between the static HTML and the browser hydration pass, producing the exact hydration mismatch this section is meant to fix. The wiring above is still required, but it does not work under Turbopack.
Until the upstream fix lands (vercel/next.js#82607), pin every next dev / next build invocation to webpack:
Webpack is still fully supported in Next 16 — Turbopack was promoted to default, not "webpack removed." The deprecation timeline for the webpack fallback hasn't been published, but the --webpack flag is a temporary workaround and is expected to be removed in a future major. Subscribe to vercel/next.js#82607 for status, and review this pin whenever Next.js publishes a webpack removal notice or the upstream Turbopack + Pages Router + MUI bug is resolved.
This is a Next.js / Turbopack bug, not a findable-ui one. Re-enable Turbopack when vercel/next.js#82607 is closed.
Wrap the app in AppCacheProvider:
import { AppCacheProvider } from "@mui/material-nextjs/v16-pagesRouter";
import type { EmotionCache } from "@emotion/react";
import type { AppProps } from "next/app";
import type { JSX } from "react";
type MyAppProps = AppProps & {
emotionCache?: EmotionCache;
};
function MyApp(props: MyAppProps): JSX.Element {
const { Component, emotionCache, pageProps } = props;
return (
<AppCacheProvider emotionCache={emotionCache}>
{/* existing theme providers and layout */}
<Component {...pageProps} />
</AppCacheProvider>
);
}
export default MyApp;Add DocumentHeadTags inside <Head> and assign documentGetInitialProps as the static getInitialProps:
import {
documentGetInitialProps,
DocumentHeadTags,
} from "@mui/material-nextjs/v16-pagesRouter";
import type { DocumentHeadTagsProps } from "@mui/material-nextjs/v16-pagesRouter";
import Document, { Head, Html, Main, NextScript } from "next/document";
import type { DocumentContext } from "next/document";
import type { JSX } from "react";
class MyDocument extends Document<DocumentHeadTagsProps> {
render(): JSX.Element {
return (
<Html>
<Head>
<DocumentHeadTags {...this.props} />
{/* other head content */}
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
MyDocument.getInitialProps = async (ctx: DocumentContext) => {
return await documentGetInitialProps(ctx);
};
export default MyDocument;Consumers using next-mdx-remote to render MDX content must pass blockJS: false to serialize(). Version 6 added a blockJS: true default that strips all JavaScript expressions from MDX during compilation — including JSX attribute expressions like <Breadcrumbs breadcrumbs={[...]} />. With the default, those props are silently dropped at build time and the receiving component renders with no props. Setting blockJS: false preserves expression-valued attributes. The narrower blockDangerousJS: true setting (default) still blocks eval / new Function / etc., so the safety net for actually-dangerous patterns is retained.
import { serialize } from "next-mdx-remote/serialize";
const mdxSource = await serialize(content, {
blockJS: false,
// ...your existing options
});If you use findable-ui's buildStaticProps helper (@databiosphere/findable-ui/lib/utils/mdx/staticGeneration/staticProps), blockJS: false is already the default — no consumer change needed.
Some consumers added the following block to package.json in late 2025 to address npm audit findings that stemmed from Next.js 14's transitive deps:
"overrides": {
"glob": "^11.0.4"
}With Next 16 and the current dep graph, this override is no longer doing useful work and is in fact silently downgrading newer glob consumers (e.g. @joshwooding/vite-plugin-react-docgen-typescript, which declares glob ^13). As of 2026-06-03, npm audit reports no findings catalogued against the resolved versions (glob@7.2.3, glob@11.1.0, glob@13.0.6) — re-check on upgrade, since the advisory landscape changes.
If your consumer carries this override, remove the block from package.json and run npm install to refresh the lockfile. Expect npm warn deprecated warnings for glob@7.x (the maintainer marks pre-v9 as deprecated) and inflight@1.0.6 (re-introduced via Jest 29's transitive chain). Both are warnings, not build/CI failures.
Use scripts/link.sh to build and install a local copy of findable-ui
into a consuming project (e.g. ncpi-dataset-catalog, data-browser):
-
Clone this repository into the same parent folder as the consuming app.
-
Set
nodeversion to22.13.0. -
Run
npm installin both repositories. -
From the consuming project directory:
../findable-ui/scripts/link.sh
This compiles TypeScript, packs the output, installs it into
node_modules, clears the Next.js cache, and starts the dev server. -
To iterate: Ctrl+C the dev server, make changes in findable-ui, and hit up-arrow to re-run the script.
-
To restore the published version:
../findable-ui/scripts/unlink.sh
Consuming projects can optionally add thin wrappers in their package.json scripts:
{
"scripts": {
"link-findable": "../findable-ui/scripts/link.sh",
"unlink-findable": "../findable-ui/scripts/unlink.sh"
}
}