create-nextly-app 0.0.2-alpha.71 → 0.0.2-alpha.73

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,3 +1,3 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_create_nextly = require("./create-nextly-BbKGDrEC.cjs");
2
+ const require_create_nextly = require("./create-nextly-Q62KZJib.cjs");
3
3
  exports.createNextly = require_create_nextly.createNextly;
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- export { t as createNextly } from "./create-nextly-5wiGMVI6.mjs";
1
+ export { t as createNextly } from "./create-nextly-CuWn_ITx.mjs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-nextly-app",
3
- "version": "0.0.2-alpha.71",
3
+ "version": "0.0.2-alpha.73",
4
4
  "description": "CLI to scaffold Nextly in your Next.js project",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -37,10 +37,10 @@
37
37
  },
38
38
  "devDependencies": {
39
39
  "@babel/types": "^7.29.7",
40
- "@nextlyhq/eslint-plugin": "0.0.2-alpha.71",
41
- "@nextlyhq/module-specifiers": "0.0.2-alpha.71",
42
- "@nextlyhq/telemetry": "0.0.2-alpha.71",
43
- "@nextlyhq/tsconfig": "0.0.2-alpha.71",
40
+ "@nextlyhq/eslint-plugin": "0.0.2-alpha.73",
41
+ "@nextlyhq/module-specifiers": "0.0.2-alpha.73",
42
+ "@nextlyhq/telemetry": "0.0.2-alpha.73",
43
+ "@nextlyhq/tsconfig": "0.0.2-alpha.73",
44
44
  "@testing-library/react": "^16.3.0",
45
45
  "@types/fs-extra": "^11.0.4",
46
46
  "@types/node": "^22.20.5",
@@ -7,7 +7,7 @@ description: >-
7
7
  code, and extra fields on users. Also use when asked to add login or
8
8
  sign-up to a Nextly site, so the answer matches what Nextly provides.
9
9
  metadata:
10
- nextly-version: 0.0.2-alpha.71
10
+ nextly-version: 0.0.2-alpha.73
11
11
  ---
12
12
 
13
13
  # Authentication and access in Nextly
@@ -150,10 +150,10 @@ export async function GET(request: Request) {
150
150
 
151
151
  - The route must live under `/admin`, or the browser never sends the session
152
152
  cookie to it.
153
- - `session.user.roleIds` holds role **IDs**, not role slugs, so
154
- `hasRole(user, "editor")` from `nextly/auth` does not match by slug in this
155
- release. Use `isSuperAdmin(user.id)`, or put role decisions in collection
156
- access rules, where `roles` are slugs.
153
+ - `session.user.roleIds` holds role **IDs**, not role slugs, and `nextly/auth`
154
+ has no role checks. Use `isSuperAdmin(user.id)`, check a permission with
155
+ `nextly.access.check`, or put role decisions in collection access rules,
156
+ where `roles` are slugs.
157
157
 
158
158
  ## Extra fields on users
159
159
 
@@ -9,7 +9,7 @@ description: >-
9
9
  not appear in the admin, a field change does not reach the database, or a
10
10
  page reading content fails to build.
11
11
  metadata:
12
- nextly-version: 0.0.2-alpha.71
12
+ nextly-version: 0.0.2-alpha.73
13
13
  ---
14
14
 
15
15
  # Define a collection in Nextly
@@ -176,9 +176,15 @@ export default async function PostsPage() {
176
176
  - `status: "published"` keeps drafts off the page. Without it a trusted
177
177
  server read returns drafts too.
178
178
  - `cachedFind` with `nextlyTags("posts")` caches the result and clears it when
179
- a post is saved, so the page updates on publish. It also lets the page build
180
- in an app with `cacheComponents: true`, where an uncached database read
181
- outside `<Suspense>` fails `next build`.
179
+ a post is saved, so the page updates on publish. It also lets the page
180
+ build: a Nextly app runs with `cacheComponents: true`, where an uncached
181
+ database read outside `<Suspense>` fails `next build`. There the read goes
182
+ through the app's own `"use cache"` function, `src/nextly/cached-read.ts`
183
+ (`nextly/cached-read.ts` without a `src/` folder): import it in the module
184
+ that reads, `import "@/nextly/cached-read";` with `src/`, or by a relative
185
+ path without one. A blog ships it; for another app, copy it from the caching
186
+ guide
187
+ (<https://nextlyhq.com/docs/guides/isr-caching#cache-components>).
182
188
  - `keyParts` names the cache entry, and everyone whose read uses the same key
183
189
  gets the same rows. Cache only reads that are the same for every reader:
184
190
  published content of a collection with no read rule, on the collection or
@@ -8,7 +8,7 @@ description: >-
8
8
  Also use when a deployed Nextly app has missing tables, refuses to start,
9
9
  or loses uploaded files.
10
10
  metadata:
11
- nextly-version: 0.0.2-alpha.71
11
+ nextly-version: 0.0.2-alpha.73
12
12
  ---
13
13
 
14
14
  # Deploy a Nextly project
@@ -244,5 +244,7 @@ could then pick their own address.
244
244
  - **Sign-in fails with `CSRF_FAILED` behind a proxy:** add the public origin
245
245
  to `NEXTLY_ALLOWED_ORIGINS`.
246
246
  - **`next build` fails on a page reading content:** in an app with
247
- `cacheComponents: true`, wrap the read in `cachedFind` (the
248
- `nextly-collections` skill) or render it inside `<Suspense>`.
247
+ `cacheComponents: true`, the Nextly default, wrap the read in `cachedFind`
248
+ (the `nextly-collections` skill) or render it inside `<Suspense>`. An app
249
+ made by an earlier release may have it off: check `next.config` first, and
250
+ turn it on as the installation guide describes.
@@ -8,7 +8,7 @@ description: >-
8
8
  Nextly install shows no collections, fails to load nextly.config.ts, or its
9
9
  admin panel misbehaves after setup.
10
10
  metadata:
11
- nextly-version: 0.0.2-alpha.71
11
+ nextly-version: 0.0.2-alpha.73
12
12
  ---
13
13
 
14
14
  # Install Nextly into an existing Next.js app
@@ -23,9 +23,10 @@ Checked against `nextly` 0.0.2-alpha.67 with Next.js 16.4. Run
23
23
 
24
24
  ## Before you start
25
25
 
26
- - Next.js 16.4.1 or later, or 15.5.28 or later on the 15 line, with the App
27
- Router (`app/` or `src/app/`). Earlier versions carry the October 2026
28
- security advisories these releases fix.
26
+ - Next.js `^16.4.1` and React `^19.3.0` (16.4.1 or later within 16.x, 19.3.0
27
+ or later within 19.x), with the App Router (`app/` or `src/app/`). The
28
+ [supported versions](https://nextlyhq.com/docs/getting-started/installation#requirements)
29
+ are kept current there.
29
30
  - Node.js `^22.12.0 || >=24.0.0`. Node 20 and Node 23 are not supported.
30
31
  - A clean git working tree, so every change below is visible in one diff.
31
32
 
@@ -1,3 +1,5 @@
1
+ import { connection } from "next/server";
2
+ import { Suspense } from "react";
1
3
  import { getBrandingCss } from "nextly/config";
2
4
 
3
5
  import config from "../../../../nextly.config";
@@ -14,7 +16,19 @@ export default function AdminLayout({
14
16
  {brandingCss && (
15
17
  <style dangerouslySetInnerHTML={{ __html: brandingCss }} />
16
18
  )}
17
- {children}
19
+ <Suspense fallback={null}>
20
+ <ForThisRequest>{children}</ForThisRequest>
21
+ </Suspense>
18
22
  </>
19
23
  );
20
24
  }
25
+
26
+ /**
27
+ * The admin renders for each request, never from a cache another user's
28
+ * request filled. Beneath the layout's Suspense boundary, so under Cache
29
+ * Components the shell above it still prerenders.
30
+ */
31
+ async function ForThisRequest({ children }: { children: React.ReactNode }) {
32
+ await connection();
33
+ return children;
34
+ }
@@ -47,8 +47,19 @@ body {
47
47
 
48
48
  /* Visible focus ring for keyboard users. `:focus-visible` only fires
49
49
  on keyboard / programmatic focus, so clicking a button doesn't
50
- show the ring but tabbing to it does. */
51
- *:focus-visible {
50
+ show the ring but tabbing to it does.
51
+
52
+ Weighed as two element names, the pseudo-class inside `:where()` counting
53
+ for nothing. That is enough to keep the ring over a reset added later, such
54
+ as `button { outline: none }`, and on this site's pages it draws as
55
+ `*:focus-visible` did, since no other rule here sets these properties on a
56
+ focused element. The admin at /admin renders in this same document and
57
+ reverts a site's rules made of element names only; with the pseudo-class
58
+ counted, this rule would outweigh that and redraw every focused control in
59
+ the admin with this ring and corner. The trade: a class-based reset, such as
60
+ `.btn { outline: none }`, outweighs this ring wherever it is written; give
61
+ such a rule a `:focus-visible` style of its own. */
62
+ html body :where(:focus-visible) {
52
63
  outline: 2px solid currentColor;
53
64
  outline-offset: 2px;
54
65
  border-radius: 2px;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Local uploads, served at the URLs Nextly gives them.
3
+ *
4
+ * Nextly's default storage writes uploads to `public/uploads` and links them
5
+ * as `/uploads/...`. `next start` serves only the files `public/` held when
6
+ * it started, so without this route a file uploaded since answers 404 until
7
+ * the server restarts. Next.js serves the files it has first; this answers
8
+ * the rest, at the same URLs, and answers 404 when media is kept in cloud
9
+ * storage instead.
10
+ *
11
+ * If you set `baseUrl` on local storage, move this folder to match it.
12
+ */
13
+
14
+ import { createUploadsHandlers } from "nextly/api/uploads";
15
+
16
+ import config from "@nextly-config";
17
+
18
+ const handlers = createUploadsHandlers({ config });
19
+
20
+ export const GET = handlers.GET;
21
+ export const HEAD = handlers.HEAD;
@@ -1,4 +1,5 @@
1
1
  import Link from "next/link";
2
+ import { Suspense } from "react";
2
3
 
3
4
  import { ThemeToggle } from "@/components/ThemeToggle";
4
5
  import { SITE_URL } from "@/lib/site-url";
@@ -12,6 +13,10 @@ import { SITE_URL } from "@/lib/site-url";
12
13
  * primary button label flips between "Set up admin" (no super-admin
13
14
  * yet) and "Open admin" (super-admin exists).
14
15
  *
16
+ * The app runs with Cache Components on, so the page is prerendered as a
17
+ * static shell and the setup-status read, made per request, sits inside a
18
+ * `<Suspense>` boundary: the shell shows "Open admin" until it resolves.
19
+ *
15
20
  * If the auth API fails for any reason (DB not migrated, network),
16
21
  * the button defaults to "Open admin" / /admin since /admin already
17
22
  * redirects to /admin/setup when no super-admin exists — the link
@@ -37,12 +42,42 @@ async function fetchSetupStatus(): Promise<SetupStatus | null> {
37
42
  }
38
43
  }
39
44
 
40
- export default async function HomePage() {
45
+ /** The primary button, linking to the admin with the label and target it needs. */
46
+ function AdminButton({ label, href }: { label: string; href: string }) {
47
+ return (
48
+ <Link href={href} className="btn-primary">
49
+ <span>{label}</span>
50
+ <svg
51
+ xmlns="http://www.w3.org/2000/svg"
52
+ width="18"
53
+ height="18"
54
+ viewBox="0 0 24 24"
55
+ fill="none"
56
+ stroke="currentColor"
57
+ strokeWidth="2"
58
+ strokeLinecap="round"
59
+ strokeLinejoin="round"
60
+ className="lucide lucide-arrow-right"
61
+ >
62
+ <path d="M5 12h14" />
63
+ <path d="m12 5 7 7-7 7" />
64
+ </svg>
65
+ </Link>
66
+ );
67
+ }
68
+
69
+ /** The button once setup-status answers: "Set up admin" while there is no super-admin. */
70
+ async function SetupAwareAdminButton() {
41
71
  const status = await fetchSetupStatus();
42
72
  const needsSetup = status?.requiresInitialUser ?? false;
43
- const adminLabel = needsSetup ? "Set up admin" : "Open admin";
44
- const adminHref = needsSetup ? "/admin/setup" : "/admin";
73
+ return needsSetup ? (
74
+ <AdminButton label="Set up admin" href="/admin/setup" />
75
+ ) : (
76
+ <AdminButton label="Open admin" href="/admin" />
77
+ );
78
+ }
45
79
 
80
+ export default function HomePage() {
46
81
  return (
47
82
  <main className="relative min-h-screen flex flex-col items-center justify-center p-6 bg-background text-foreground font-display selection:bg-foreground/10">
48
83
  <ThemeToggle />
@@ -85,24 +120,11 @@ export default async function HomePage() {
85
120
  </p>
86
121
 
87
122
  <div className="mt-10 flex flex-wrap items-center justify-center gap-4">
88
- <Link href={adminHref} className="btn-primary">
89
- <span>{adminLabel}</span>
90
- <svg
91
- xmlns="http://www.w3.org/2000/svg"
92
- width="18"
93
- height="18"
94
- viewBox="0 0 24 24"
95
- fill="none"
96
- stroke="currentColor"
97
- strokeWidth="2"
98
- strokeLinecap="round"
99
- strokeLinejoin="round"
100
- className="lucide lucide-arrow-right"
101
- >
102
- <path d="M5 12h14" />
103
- <path d="m12 5 7 7-7 7" />
104
- </svg>
105
- </Link>
123
+ {/* "/admin" redirects to setup while there is no super-admin, so the
124
+ fallback link works before the read answers. */}
125
+ <Suspense fallback={<AdminButton label="Open admin" href="/admin" />}>
126
+ <SetupAwareAdminButton />
127
+ </Suspense>
106
128
  <Link href="https://nextlyhq.com/docs" className="btn-secondary">
107
129
  <span>Read documentation</span>
108
130
  <svg
@@ -24,8 +24,9 @@ export async function register(): Promise<void> {
24
24
  const { createRegister } = await import("nextly");
25
25
  const { default: config } = await import("../nextly.config");
26
26
  // Next.js writes its Cache Components setting into this file as it builds
27
- // it. Nextly, loaded from node_modules, cannot read that for itself, and
28
- // warns when Cache Components is on, which it does not support yet.
27
+ // it. Nextly, loaded from node_modules, cannot read that for itself. With
28
+ // it off, Nextly warns at start; with it on, a cached read that finds no
29
+ // "use cache" read function says so.
29
30
  await createRegister(config, {
30
31
  cacheComponents: process.env.__NEXT_CACHE_COMPONENTS,
31
32
  })();
@@ -6,8 +6,14 @@ import type { NextConfig } from "next";
6
6
  // external (Turbopack can't bundle them).
7
7
  const nextConfig: NextConfig = {
8
8
  typescript: { ignoreBuildErrors: true },
9
- // Nextly does not support Cache Components yet; see the installation guide.
10
- cacheComponents: false,
9
+ // Nextly runs with Cache Components and Partial Prefetching on. Its cache
10
+ // handlers make the first read after a write show the new version:
11
+ // https://nextlyhq.com/docs/getting-started/installation#cache-components
12
+ cacheComponents: true,
13
+ partialPrefetching: true,
14
+ cacheHandler: require.resolve("nextly/cache-handler"),
15
+ cacheMaxMemorySize: 0,
16
+ cacheHandlers: { default: require.resolve("nextly/cache-handlers") },
11
17
  // Transpile the aliased plugin source so HMR fires on src/ edits.
12
18
  transpilePackages: ["{{pluginName}}"],
13
19
  serverExternalPackages: [
@@ -0,0 +1,24 @@
1
+ import { connection } from "next/server";
2
+ import { Suspense } from "react";
3
+
4
+ export default function AdminLayout({
5
+ children,
6
+ }: {
7
+ children: React.ReactNode;
8
+ }) {
9
+ return (
10
+ <Suspense fallback={null}>
11
+ <ForThisRequest>{children}</ForThisRequest>
12
+ </Suspense>
13
+ );
14
+ }
15
+
16
+ /**
17
+ * The admin renders for each request, never from a cache another user's
18
+ * request filled. Beneath the layout's Suspense boundary, so under Cache
19
+ * Components the rest of the page still prerenders.
20
+ */
21
+ async function ForThisRequest({ children }: { children: React.ReactNode }) {
22
+ await connection();
23
+ return children;
24
+ }
@@ -8,8 +8,9 @@ export async function register(): Promise<void> {
8
8
  const { createRegister, getService } = await import("nextly");
9
9
  const { default: config } = await import("../nextly.config");
10
10
  // Next.js writes its Cache Components setting into this file as it builds
11
- // it. Nextly, loaded from node_modules, cannot read that for itself, and
12
- // warns when Cache Components is on, which it does not support yet.
11
+ // it. Nextly, loaded from node_modules, cannot read that for itself. With
12
+ // it off, Nextly warns at start; with it on, a cached read that finds no
13
+ // "use cache" read function says so.
13
14
  await createRegister(config, {
14
15
  cacheComponents: process.env.__NEXT_CACHE_COMPONENTS,
15
16
  })();