diff --git a/templates/content/app/components/editor/DocumentEditor.tsx b/templates/content/app/components/editor/DocumentEditor.tsx
index 55ee18867f..f876d38f79 100644
--- a/templates/content/app/components/editor/DocumentEditor.tsx
+++ b/templates/content/app/components/editor/DocumentEditor.tsx
@@ -85,6 +85,7 @@ import { cn } from "@/lib/utils";
import {
documentBodyHydrationIsPending,
isEffectivelyEmptyDocumentContent,
+ newDocumentPageChoiceIsDisabled,
} from "./body-hydration";
import { BuilderBodySyncingNotice } from "./BuilderBodySyncingNotice";
import type { CommentTextAnchor } from "./comment-anchors";
@@ -1779,9 +1780,12 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
type="button"
variant="outline"
className="justify-start gap-2"
- disabled={
- !editorCanEdit || createDatabase.isPending
- }
+ disabled={newDocumentPageChoiceIsDisabled({
+ canEdit,
+ bodyHydrationPending,
+ databaseCreationPending:
+ createDatabase.isPending,
+ })}
onClick={handleChoosePage}
>
diff --git a/templates/content/app/components/editor/body-hydration.test.ts b/templates/content/app/components/editor/body-hydration.test.ts
index 14c395fc46..8736af0851 100644
--- a/templates/content/app/components/editor/body-hydration.test.ts
+++ b/templates/content/app/components/editor/body-hydration.test.ts
@@ -7,6 +7,7 @@ import {
databaseItemBodyHydrationIsPending,
documentBodyHydrationIsPending,
isEffectivelyEmptyDocumentContent,
+ newDocumentPageChoiceIsDisabled,
previewBodyHydrationIsPending,
previewBodyHydrationIsTerminalError,
previewDraftConflictsWithHydratedBody,
@@ -291,6 +292,40 @@ describe("body hydration editing gates", () => {
expect(isEffectivelyEmptyDocumentContent("Hydrated body")).toBe(false);
});
+ it("keeps the page choice usable while collaboration connects", () => {
+ expect(
+ newDocumentPageChoiceIsDisabled({
+ canEdit: true,
+ bodyHydrationPending: false,
+ databaseCreationPending: false,
+ }),
+ ).toBe(false);
+ });
+
+ it("disables the page choice for viewers, body hydration, or database conversion", () => {
+ expect(
+ newDocumentPageChoiceIsDisabled({
+ canEdit: false,
+ bodyHydrationPending: false,
+ databaseCreationPending: false,
+ }),
+ ).toBe(true);
+ expect(
+ newDocumentPageChoiceIsDisabled({
+ canEdit: true,
+ bodyHydrationPending: true,
+ databaseCreationPending: false,
+ }),
+ ).toBe(true);
+ expect(
+ newDocumentPageChoiceIsDisabled({
+ canEdit: true,
+ bodyHydrationPending: false,
+ databaseCreationPending: true,
+ }),
+ ).toBe(true);
+ });
+
it("ignores untouched empty preview normalization before it can dirty-save", () => {
expect(
shouldIgnorePreviewEmptyNormalization({
diff --git a/templates/content/app/components/editor/body-hydration.ts b/templates/content/app/components/editor/body-hydration.ts
index 4b0b11918c..4131abfa01 100644
--- a/templates/content/app/components/editor/body-hydration.ts
+++ b/templates/content/app/components/editor/body-hydration.ts
@@ -69,6 +69,16 @@ export function documentBodyHydrationIsPending(
return builderBodyHydrationIsPending(hydration);
}
+export function newDocumentPageChoiceIsDisabled(args: {
+ canEdit: boolean;
+ bodyHydrationPending: boolean;
+ databaseCreationPending: boolean;
+}) {
+ return (
+ !args.canEdit || args.bodyHydrationPending || args.databaseCreationPending
+ );
+}
+
export function previewBodyHydrationIsPending(args: {
item: Pick;
document: Pick | null | undefined;
diff --git a/templates/content/app/root.revalidation.test.ts b/templates/content/app/root.revalidation.test.ts
new file mode 100644
index 0000000000..bbe2653b52
--- /dev/null
+++ b/templates/content/app/root.revalidation.test.ts
@@ -0,0 +1,39 @@
+import type { ShouldRevalidateFunctionArgs } from "react-router";
+import { describe, expect, it } from "vitest";
+
+import { shouldRevalidate } from "./root";
+
+function revalidationArgs(
+ overrides: Partial = {},
+): ShouldRevalidateFunctionArgs {
+ return {
+ currentUrl: new URL("https://content.test/page/previous"),
+ currentParams: { id: "previous" },
+ nextUrl: new URL("https://content.test/page/next"),
+ nextParams: { id: "next" },
+ defaultShouldRevalidate: true,
+ ...overrides,
+ } as ShouldRevalidateFunctionArgs;
+}
+
+describe("root route revalidation", () => {
+ it("does not block ordinary page navigations on bootstrap locale data", () => {
+ expect(shouldRevalidate(revalidationArgs())).toBe(false);
+ });
+
+ it("retains React Router's default revalidation for action submissions", () => {
+ expect(
+ shouldRevalidate(
+ revalidationArgs({ formMethod: "POST", defaultShouldRevalidate: true }),
+ ),
+ ).toBe(true);
+ expect(
+ shouldRevalidate(
+ revalidationArgs({
+ formMethod: "POST",
+ defaultShouldRevalidate: false,
+ }),
+ ),
+ ).toBe(false);
+ });
+});
diff --git a/templates/content/app/root.tsx b/templates/content/app/root.tsx
index c85a6cb324..8e8b0229e3 100644
--- a/templates/content/app/root.tsx
+++ b/templates/content/app/root.tsx
@@ -49,7 +49,11 @@ import {
useRouteLoaderData,
useRouteError,
} from "react-router";
-import type { LinksFunction, LoaderFunctionArgs } from "react-router";
+import type {
+ LinksFunction,
+ LoaderFunctionArgs,
+ ShouldRevalidateFunctionArgs,
+} from "react-router";
// Styled sonner wrapper — passed via AppProviders `toaster` prop to avoid duplicate.
import { Toaster as Sonner } from "@/components/ui/sonner";
@@ -105,6 +109,13 @@ export async function loader({
};
}
+export function shouldRevalidate({
+ defaultShouldRevalidate,
+ formMethod,
+}: ShouldRevalidateFunctionArgs) {
+ return formMethod ? defaultShouldRevalidate : false;
+}
+
// Pass args to match content's 3-way theme-cycle UX (no disableTransitionOnChange).
const THEME_INIT_SCRIPT = getThemeInitScript("system", true);
diff --git a/templates/content/changelog/2026-07-23-new-pages-open-immediately-while-slow-network-work-continues.md b/templates/content/changelog/2026-07-23-new-pages-open-immediately-while-slow-network-work-continues.md
new file mode 100644
index 0000000000..72ec59a37e
--- /dev/null
+++ b/templates/content/changelog/2026-07-23-new-pages-open-immediately-while-slow-network-work-continues.md
@@ -0,0 +1,6 @@
+---
+type: fixed
+date: 2026-07-23
+---
+
+New pages open immediately while slow network work continues