Conversation
It redirects to jspecify.dev/docs/api, which in turn redirects to jspecify.dev/docs/api/org/jspecify/annotations/package-summary.html. "jspecify.dev/api" is not dramatically shorter, but I figure that every bit helps. This also follows some precendent from some Google libraries, including guava.dev/api and truth.dev/api. We could consider further following Guava precedent by adding jspecify.dev/Nullable, jspecify.dev/NullMarked, etc. As for the implementation approach: Gemini says: > Here is why a static HTML redirect in `docs/static/` is the most correct and idiomatic approach for this project: > > ### 1. Why not `@docusaurus/plugin-client-redirects`? > While Docusaurus has an official redirect plugin (`@docusaurus/plugin-client-redirects`), that plugin validates during the static SSR compilation step that every destination (`to`) path matches an internal route known to Docusaurus. Because your Javadoc at `/docs/api/` is copied into the output folder by Gradle (`copyDocs`) **after** Docusaurus completes its build, the redirect plugin would reject `/docs/api/` as an unrecognized route. > > ### 2. How `docs/static/api/index.html` works on GitHub Pages > * **Docusaurus static asset handling**: Any file placed in `docs/static/` is copied as-is to the root of the output `build/` directory during `docusaurus build` (or `./gradlew buildDocs`). > * **GitHub Pages static hosting**: Because GitHub Pages is purely static hosting (and `.nojekyll` disables Jekyll redirect plugins), client-side HTML redirects using `<meta http-equiv="refresh">` combined with JavaScript are the standard mechanism. > * When a user visits `https://jspecify.dev/api` (or `/api/`), GitHub Pages serves `build/api/index.html`, which immediately redirects the browser to `/docs/api/`. And when I asked about avoiding the "Javadoc... is copied into the output folder by Gradle (`copyDocs`) **after** Docusaurus completes its build" problem by having Gradle copy its ouputs *before* Docusaurus runs, Gemini said: > Here is a breakdown of what would happen if Gradle copied them earlier, why it wouldn't help with Docusaurus plugins, and the architectural tradeoffs: > > --- > > ### 1. It wouldn't change how Docusaurus plugins (like redirect) behave > In Docusaurus, the internal routing table is built strictly from **React and Markdown source components** (`docs/`, `blog/`, `src/pages/`). > * Neither existing HTML files in the output directory (`build/`) nor raw assets in `static/` are ever registered as internal Single Page Application (SPA) routes. > * Therefore, even if Javadoc files were present before Docusaurus started compiling, plugins like `@docusaurus/plugin-client-redirects`, `sitemap`, and the SPA broken-link checker would still treat `/docs/api/` as an unmanaged external asset path rather than an internal route. > > --- > > ### 2. Option A: Copying into `docs/build/` before Docusaurus runs > If Gradle copied Javadoc into `docs/build/docs/api/` before executing `npm run build`: > * **Result**: `docusaurus build` clears the target output directory (`build/`) by default at the very start of its compilation step. Anything placed in `build/` before Docusaurus runs would be instantly wiped out. > > --- > > ### 3. Option B: Copying into `docs/static/docs/api/` before Docusaurus runs > To make Docusaurus preserve and copy the files itself, Gradle would have to output generated Javadoc into Docusaurus's source asset directory (`docs/static/docs/api/`). While this would technically work, it introduces significant downsides: > > * **Polluting the source tree with build artifacts**: You would be writing hundreds or thousands of generated HTML/CSS files directly into your source directory (`docs/static/`). This clutters IDE indexing, file searches, and working directory status, requiring new `.gitignore` rules to ignore the generated subfolder. > * **Risk of serving stale Javadoc**: If a developer ran `npm start` or `npm run build` directly without running Gradle first, they might silently serve stale Javadoc leftover in `docs/static/` from a build weeks ago without realizing it. > * **Slower Docusaurus builds**: During compilation, Webpack enumerates and copies every single asset in `static/` over to `build/`. Forcing Webpack to process thousands of Javadoc files slows down Docusaurus's build. Letting Gradle handle the Javadoc file copying directly from one build folder to another is much faster and more efficient. > * **The only pro**: If Javadoc were in `docs/static/docs/api/`, it *would* be accessible during local development when running `npm start`. However, because you can already preview everything cleanly by running `./gradlew buildDocs && npm run serve` (as documented in your README.md \[cpovirk note: at least after https://github.com/jspecify/jspecify/pull/847!\]), polluting the source tree is not worth this minor benefit. > > ### Conclusion > Keeping your source tree (`docs/`) strictly separated from build outputs (`docs/build/`) and having Gradle's `copyDocs` task assemble the final site post-build is the cleanest, most performant, and most idiomatic architecture.
cpovirk
added a commit
that referenced
this pull request
Jul 27, 2026
…ges over `npm start`. The advantages were on display as I worked on #849 and #858. Here's an example of an error that was caught by `./gradlew buildDocs` / `npm run build` but not `npm start`. It came from some bad link syntax when I tried to follow (roughly) `[@NullMarked]` with a parenthetical "(Why?)": ``` [ERROR] Error: Unable to build website for locale en. at tryToBuildLocale (/usr/local/google/home/cpovirk/clients/jspecify-white/jspecify/docs/node_modules/@docusaurus/core/lib/commands/build/build.js:78:15) at async /usr/local/google/home/cpovirk/clients/jspecify-white/jspecify/docs/node_modules/@docusaurus/core/lib/commands/build/build.js:34:9 ... 4 lines matching cause stack trace ... at async file:///usr/local/google/home/cpovirk/clients/jspecify-white/jspecify/docs/node_modules/@docusaurus/core/bin/docusaurus.mjs:44:3 { [cause]: Error: Docusaurus found broken links! Please check the pages of your site in the list below, and make sure you don't reference any path that does not exist. Note: it's possible to ignore broken links with the 'onBrokenLinks' Docusaurus configuration, and let the build pass. Exhaustive list of all broken links found: - Broken link on source page path = /docs/user-guide/: -> linking to %5BWhy/?%5D(nullness-design-faq.md#null-marked-non-null-by-default) (resolved as: /docs/user-guide/%5BWhy/?%5D(nullness-design-faq.md#null-marked-non-null-by-default)) at throwError (/usr/local/google/home/cpovirk/clients/jspecify-white/jspecify/docs/node_modules/@docusaurus/logger/lib/logger.js:80:11) at reportBrokenLinks (/usr/local/google/home/cpovirk/clients/jspecify-white/jspecify/docs/node_modules/@docusaurus/core/lib/server/brokenLinks.js:250:47) at handleBrokenLinks (/usr/local/google/home/cpovirk/clients/jspecify-white/jspecify/docs/node_modules/@docusaurus/core/lib/server/brokenLinks.js:282:5) at executeBrokenLinksCheck (/usr/local/google/home/cpovirk/clients/jspecify-white/jspecify/docs/node_modules/@docusaurus/core/lib/commands/build/buildLocale.js:91:47) at /usr/local/google/home/cpovirk/clients/jspecify-white/jspecify/docs/node_modules/@docusaurus/core/lib/commands/build/buildLocale.js:70:67 at Object.async (/usr/local/google/home/cpovirk/clients/jspecify-white/jspecify/docs/node_modules/@docusaurus/logger/lib/perfLogger.js:42:47) at buildLocale (/usr/local/google/home/cpovirk/clients/jspecify-white/jspecify/docs/node_modules/@docusaurus/core/lib/commands/build/buildLocale.js:70:31) at async runBuildLocaleTask (/usr/local/google/home/cpovirk/clients/jspecify-white/jspecify/docs/node_modules/@docusaurus/core/lib/commands/build/build.js:93:5) at async /usr/local/google/home/cpovirk/clients/jspecify-white/jspecify/docs/node_modules/@docusaurus/core/lib/commands/build/build.js:74:13 at async tryToBuildLocale (/usr/local/google/home/cpovirk/clients/jspecify-white/jspecify/docs/node_modules/@docusaurus/core/lib/commands/build/build.js:70:9) at async /usr/local/google/home/cpovirk/clients/jspecify-white/jspecify/docs/node_modules/@docusaurus/core/lib/commands/build/build.js:34:9 at async mapAsyncSequential (/usr/local/google/home/cpovirk/clients/jspecify-white/jspecify/docs/node_modules/@docusaurus/utils/lib/jsUtils.js:21:24) at async Command.build (/usr/local/google/home/cpovirk/clients/jspecify-white/jspecify/docs/node_modules/@docusaurus/core/lib/commands/build/build.js:33:5) at async Promise.all (index 0) at async runCLI (/usr/local/google/home/cpovirk/clients/jspecify-white/jspecify/docs/node_modules/@docusaurus/core/lib/commands/cli.js:56:5) at async file:///usr/local/google/home/cpovirk/clients/jspecify-white/jspecify/docs/node_modules/@docusaurus/core/bin/docusaurus.mjs:44:3 ```
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
It redirects to jspecify.dev/docs/api, which in turn redirects to jspecify.dev/docs/api/org/jspecify/annotations/package-summary.html.
"jspecify.dev/api" is not dramatically shorter, but I figure that every bit helps. This also follows some precendent from some Google libraries, including guava.dev/api and truth.dev/api. We could consider further following Guava precedent by adding jspecify.dev/Nullable, jspecify.dev/NullMarked, etc.
As for the implementation approach: Gemini says:
And when I asked about avoiding the "Javadoc... is copied into the output folder by Gradle (
copyDocs) after Docusaurus completes its build" problem by having Gradle copy its ouputs before Docusaurus runs, Gemini said: