create-nextly-app 0.0.2-alpha.66 → 0.0.2-alpha.68
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/README.md +29 -8
- package/dist/{chunk-I2U7KBH6.mjs → chunk-XCYSH46I.mjs} +36805 -10917
- package/dist/chunk-XCYSH46I.mjs.map +1 -0
- package/dist/cli.cjs +27693 -1892
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.mjs +17 -106
- package/dist/cli.mjs.map +1 -1
- package/dist/index.cjs +36591 -10708
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.mjs +1 -1
- package/package.json +18 -7
- package/skills/nextly-authentication/SKILL.md +177 -0
- package/skills/nextly-collections/SKILL.md +215 -0
- package/skills/nextly-deploy/SKILL.md +246 -0
- package/skills/nextly-install/SKILL.md +122 -0
- package/templates/base/.env.example +3 -5
- package/templates/base/AGENTS.md.template +31 -0
- package/templates/base/src/types/generated/nextly-types.ts +6 -4
- package/templates/blank/src/app/(frontend)/components/ThemeToggle.tsx +55 -24
- package/templates/blank/src/instrumentation.ts +27 -0
- package/templates/plugin/AGENTS.md.template +6 -4
- package/templates/plugin/README.md +4 -0
- package/templates/plugin/dev/.env.example +5 -2
- package/templates/plugin/dev/src/app/admin/[[...params]]/page.tsx +14 -2
- package/templates/plugin/dev/src/instrumentation.ts +20 -0
- package/templates/plugin/dev/src/seed-dev-user.ts +51 -0
- package/templates/plugin/gitignore +5 -2
- package/templates/plugin/src/plugin.test.ts +9 -2
- package/dist/chunk-I2U7KBH6.mjs.map +0 -1
- package/templates/plugin/dev/instrumentation.ts +0 -10
package/dist/index.d.cts
CHANGED
|
@@ -48,6 +48,12 @@ interface CreateNextlyOptions {
|
|
|
48
48
|
localTemplatePath?: string;
|
|
49
49
|
/** Git branch for template download from GitHub (defaults to "main") */
|
|
50
50
|
branch?: string;
|
|
51
|
+
/**
|
|
52
|
+
* Whether to add the Nextly skills for coding agents to the project.
|
|
53
|
+
* False skips them without asking; otherwise the developer is asked, and
|
|
54
|
+
* defaults mode adds them.
|
|
55
|
+
*/
|
|
56
|
+
skills?: boolean;
|
|
51
57
|
}
|
|
52
58
|
|
|
53
59
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -48,6 +48,12 @@ interface CreateNextlyOptions {
|
|
|
48
48
|
localTemplatePath?: string;
|
|
49
49
|
/** Git branch for template download from GitHub (defaults to "main") */
|
|
50
50
|
branch?: string;
|
|
51
|
+
/**
|
|
52
|
+
* Whether to add the Nextly skills for coding agents to the project.
|
|
53
|
+
* False skips them without asking; otherwise the developer is asked, and
|
|
54
|
+
* defaults mode adds them.
|
|
55
|
+
*/
|
|
56
|
+
skills?: boolean;
|
|
51
57
|
}
|
|
52
58
|
|
|
53
59
|
/**
|
package/dist/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-nextly-app",
|
|
3
|
-
"version": "0.0.2-alpha.
|
|
3
|
+
"version": "0.0.2-alpha.68",
|
|
4
4
|
"description": "CLI to scaffold Nextly in your Next.js project",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -20,32 +20,43 @@
|
|
|
20
20
|
"files": [
|
|
21
21
|
"dist",
|
|
22
22
|
"bin",
|
|
23
|
+
"skills",
|
|
23
24
|
"templates"
|
|
24
25
|
],
|
|
25
26
|
"dependencies": {
|
|
27
|
+
"@babel/parser": "^7.29.7",
|
|
26
28
|
"@clack/prompts": "^1.1.0",
|
|
27
29
|
"commander": "^14.0.2",
|
|
30
|
+
"dotenv-expand": "10.0.0",
|
|
28
31
|
"execa": "^9.6.0",
|
|
29
32
|
"fs-extra": "^11.3.0",
|
|
30
33
|
"picocolors": "^1.1.1",
|
|
31
|
-
"
|
|
34
|
+
"picomatch": "^4.0.5",
|
|
35
|
+
"tar": "^7.5.19",
|
|
36
|
+
"yaml": "^2.8.3"
|
|
32
37
|
},
|
|
33
38
|
"devDependencies": {
|
|
39
|
+
"@babel/types": "^7.29.7",
|
|
40
|
+
"@nextlyhq/eslint-config": "0.0.2-alpha.68",
|
|
41
|
+
"@nextlyhq/eslint-plugin": "0.0.2-alpha.68",
|
|
42
|
+
"@nextlyhq/telemetry": "0.0.2-alpha.68",
|
|
43
|
+
"@nextlyhq/tsconfig": "0.0.2-alpha.68",
|
|
44
|
+
"@testing-library/react": "^16.3.0",
|
|
34
45
|
"@types/fs-extra": "^11.0.4",
|
|
35
46
|
"@types/node": "^20.19.17",
|
|
47
|
+
"@types/picomatch": "^4.0.3",
|
|
36
48
|
"@types/tar": "^6.1.13",
|
|
37
49
|
"@typescript-eslint/parser": "^8.59.0",
|
|
38
50
|
"@vitest/coverage-v8": "^4.1.0",
|
|
39
51
|
"@vitest/ui": "^4.1.0",
|
|
40
52
|
"eslint": "^9.39.1",
|
|
53
|
+
"jsdom": "^27.1.0",
|
|
54
|
+
"react": "19.2.0",
|
|
55
|
+
"react-dom": "19.2.0",
|
|
41
56
|
"tsup": "^8.5.0",
|
|
42
57
|
"typescript": "^5.9.3",
|
|
43
58
|
"vite-tsconfig-paths": "^5.1.4",
|
|
44
|
-
"vitest": "^4.1.0"
|
|
45
|
-
"@nextlyhq/eslint-config": "0.0.2-alpha.66",
|
|
46
|
-
"@nextlyhq/eslint-plugin": "0.0.2-alpha.66",
|
|
47
|
-
"@nextlyhq/telemetry": "0.0.2-alpha.66",
|
|
48
|
-
"@nextlyhq/tsconfig": "0.0.2-alpha.66"
|
|
59
|
+
"vitest": "^4.1.0"
|
|
49
60
|
},
|
|
50
61
|
"engines": {
|
|
51
62
|
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nextly-authentication
|
|
3
|
+
description: >-
|
|
4
|
+
Use when deciding who can sign in to a Nextly project and what they can do:
|
|
5
|
+
the first admin account, roles, access rules on collections, API keys for
|
|
6
|
+
other apps and scripts, checking the signed-in user in your own server
|
|
7
|
+
code, and extra fields on users. Also use when asked to add login or
|
|
8
|
+
sign-up to a Nextly site, so the answer matches what Nextly provides.
|
|
9
|
+
metadata:
|
|
10
|
+
nextly-version: 0.0.2-alpha.67
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Authentication and access in Nextly
|
|
14
|
+
|
|
15
|
+
Checked against `nextly` 0.0.2-alpha.67 with Next.js 16.4. Run
|
|
16
|
+
`npx nextly --version`; if it prints a newer version, prefer
|
|
17
|
+
<https://nextlyhq.com/docs/guides/authentication> where it disagrees with this
|
|
18
|
+
skill.
|
|
19
|
+
|
|
20
|
+
## What is built in, and what is not
|
|
21
|
+
|
|
22
|
+
Nextly has its own email and password sign-in for the **people who run the
|
|
23
|
+
site**: admins, editors and authors who use the admin panel at `/admin`. It is
|
|
24
|
+
not built on NextAuth or any OAuth library, and it needs no setup beyond
|
|
25
|
+
`NEXTLY_SECRET`.
|
|
26
|
+
|
|
27
|
+
Its session cookies are scoped to `/admin`. So in this release Nextly does
|
|
28
|
+
**not** provide sign-in for the visitors of your public site, nor social or
|
|
29
|
+
OAuth login. If the site needs visitor accounts, use an auth library for the
|
|
30
|
+
site and keep Nextly for content; do not try to reuse the admin sign-in for
|
|
31
|
+
visitors.
|
|
32
|
+
|
|
33
|
+
In particular, do not build a sign-in form for your own pages on the Direct
|
|
34
|
+
API's `nextly.login()`. It is for trusted server code, not for signing people
|
|
35
|
+
in, and this release has no ready path for members-only pages. Protect those
|
|
36
|
+
with the auth library the site uses.
|
|
37
|
+
|
|
38
|
+
## The secret
|
|
39
|
+
|
|
40
|
+
`NEXTLY_SECRET` signs the session tokens. The installer generates one in
|
|
41
|
+
`.env`. In production it must be at least 32 characters; generate one with:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
openssl rand -base64 32
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Never commit it, and use a different one per environment.
|
|
48
|
+
|
|
49
|
+
## The first admin
|
|
50
|
+
|
|
51
|
+
Open `/admin/setup` once, on an empty database, and create the first account.
|
|
52
|
+
It gets the **Super Admin** role, which passes every check. After that, setup
|
|
53
|
+
is closed, and people are added in the admin under **Users**.
|
|
54
|
+
|
|
55
|
+
Built-in roles: `super-admin`, `admin`, `editor`, `author`, `viewer`. Roles and
|
|
56
|
+
their permissions are managed in the admin under **Settings > Roles**.
|
|
57
|
+
|
|
58
|
+
## Access rules on a collection
|
|
59
|
+
|
|
60
|
+
Each operation takes `true`, `false` or a function. A function receives
|
|
61
|
+
`{ user, roles, permissions, operation, collection }`, where `roles` are role
|
|
62
|
+
slugs such as `"editor"` and `user` is `null` for a request with no session.
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
import { defineCollection, text } from "nextly/config";
|
|
66
|
+
import type { AccessControlFunction } from "nextly";
|
|
67
|
+
|
|
68
|
+
const signedIn: AccessControlFunction = ({ user }) => Boolean(user);
|
|
69
|
+
const editors: AccessControlFunction = ({ roles }) =>
|
|
70
|
+
roles.includes("admin") || roles.includes("editor");
|
|
71
|
+
|
|
72
|
+
export const Articles = defineCollection({
|
|
73
|
+
slug: "articles",
|
|
74
|
+
fields: [text({ name: "title", required: true })],
|
|
75
|
+
access: {
|
|
76
|
+
read: () => true,
|
|
77
|
+
create: signedIn,
|
|
78
|
+
update: editors,
|
|
79
|
+
delete: editors,
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
- A Super Admin passes every rule; test rules with another role.
|
|
85
|
+
- These rules are applied to requests to the admin API and to Direct API
|
|
86
|
+
reads made with `overrideAccess: false`. A plain server-side
|
|
87
|
+
`nextly.find(...)` is a trusted read that skips them.
|
|
88
|
+
- `read: () => true` does not make the admin API public. Every request to
|
|
89
|
+
`/admin/api/...` needs a session or an API key, and is refused with 401
|
|
90
|
+
otherwise. Public pages read content on the server with `getNextly` (the
|
|
91
|
+
`nextly-collections` skill).
|
|
92
|
+
|
|
93
|
+
To apply the rules for a particular user in server code:
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
const result = await nextly.find({
|
|
97
|
+
collection: "articles",
|
|
98
|
+
overrideAccess: false,
|
|
99
|
+
user: { id: userId },
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## API keys, for other apps and scripts
|
|
104
|
+
|
|
105
|
+
Create keys in the admin under **Settings > API Keys**. The full key is shown
|
|
106
|
+
once. Send it as a bearer token:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
curl -H "Authorization: Bearer $NEXTLY_API_KEY" \
|
|
110
|
+
"https://example.com/admin/api/collections/posts/entries?limit=10"
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
A **Read-only** key can read and is refused writes (403). **Full access**
|
|
114
|
+
carries its creator's permissions; **Role-based** carries the permissions of a
|
|
115
|
+
role you choose. Keys can expire and be revoked. Keep keys in environment
|
|
116
|
+
variables, never in client code.
|
|
117
|
+
|
|
118
|
+
## The signed-in user in your own server code
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
// src/app/admin/me/route.ts: the session cookie only reaches /admin paths
|
|
122
|
+
import { getNextly } from "nextly";
|
|
123
|
+
import { getSession, isSuperAdmin } from "nextly/auth";
|
|
124
|
+
|
|
125
|
+
import nextlyConfig from "../../../../nextly.config";
|
|
126
|
+
|
|
127
|
+
export async function GET(request: Request) {
|
|
128
|
+
// Start Nextly first: this route may be the first request a server handles.
|
|
129
|
+
await getNextly({ config: nextlyConfig });
|
|
130
|
+
const session = await getSession(request, process.env.NEXTLY_SECRET ?? "");
|
|
131
|
+
if (!session.authenticated) {
|
|
132
|
+
return Response.json({ error: "Not signed in" }, { status: 401 });
|
|
133
|
+
}
|
|
134
|
+
return Response.json({
|
|
135
|
+
email: session.user.email,
|
|
136
|
+
superAdmin: await isSuperAdmin(session.user.id),
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
- The route must live under `/admin`, or the browser never sends the session
|
|
142
|
+
cookie to it.
|
|
143
|
+
- `session.user.roleIds` holds role **IDs**, not role slugs, so
|
|
144
|
+
`hasRole(user, "editor")` from `nextly/auth` does not match by slug in this
|
|
145
|
+
release. Use `isSuperAdmin(user.id)`, or put role decisions in collection
|
|
146
|
+
access rules, where `roles` are slugs.
|
|
147
|
+
|
|
148
|
+
## Extra fields on users
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
import { defineConfig, text, textarea } from "nextly/config";
|
|
152
|
+
|
|
153
|
+
export default defineConfig({
|
|
154
|
+
users: {
|
|
155
|
+
fields: [
|
|
156
|
+
textarea({ name: "bio", maxLength: 500 }),
|
|
157
|
+
text({ name: "avatarUrl" }),
|
|
158
|
+
],
|
|
159
|
+
},
|
|
160
|
+
// collections, typescript, ...
|
|
161
|
+
});
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Read users with `nextly.users.find()`, not `find({ collection: "users" })`.
|
|
165
|
+
|
|
166
|
+
## Password reset and verification emails
|
|
167
|
+
|
|
168
|
+
These need an email provider. Without one, tokens come back in the API
|
|
169
|
+
response as a development fallback. See
|
|
170
|
+
<https://nextlyhq.com/docs/guides/email>.
|
|
171
|
+
|
|
172
|
+
## Check it worked
|
|
173
|
+
|
|
174
|
+
- `/admin/setup` creates the first account, and `/admin` signs in.
|
|
175
|
+
- A request to `/admin/api/collections/<slug>/entries` without a session or
|
|
176
|
+
key answers 401.
|
|
177
|
+
- A read-only API key reads entries and gets 403 on a write.
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nextly-collections
|
|
3
|
+
description: >-
|
|
4
|
+
Use when defining or changing content types in a Nextly project in code:
|
|
5
|
+
writing a collection with defineCollection, choosing field types, adding
|
|
6
|
+
draft and published status, registering it in nextly.config.ts, generating
|
|
7
|
+
TypeScript types, creating a migration, and reading entries from a Next.js
|
|
8
|
+
Server Component with getNextly and find. Also use when a collection does
|
|
9
|
+
not appear in the admin, a field change does not reach the database, or a
|
|
10
|
+
page reading content fails to build.
|
|
11
|
+
metadata:
|
|
12
|
+
nextly-version: 0.0.2-alpha.67
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Define a collection in Nextly
|
|
16
|
+
|
|
17
|
+
A collection is a content type with many entries: posts, products, authors.
|
|
18
|
+
Each one becomes a database table, an admin list and edit screen, and a type
|
|
19
|
+
you can query. Collections can also be made in the admin's visual Schema
|
|
20
|
+
Builder; this skill covers the code-first way.
|
|
21
|
+
|
|
22
|
+
Checked against `nextly` 0.0.2-alpha.67 with Next.js 16.4. Run
|
|
23
|
+
`npx nextly --version`; if it prints a newer version, prefer
|
|
24
|
+
<https://nextlyhq.com/docs> where it disagrees with this skill. Nextly must
|
|
25
|
+
already be installed (the `nextly-install` skill), with `esbuild` among the
|
|
26
|
+
dev dependencies.
|
|
27
|
+
|
|
28
|
+
## 1. Write the collection
|
|
29
|
+
|
|
30
|
+
`src/collections/posts.ts`:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import {
|
|
34
|
+
defineCollection,
|
|
35
|
+
text,
|
|
36
|
+
textarea,
|
|
37
|
+
richText,
|
|
38
|
+
date,
|
|
39
|
+
select,
|
|
40
|
+
} from "nextly/config";
|
|
41
|
+
|
|
42
|
+
export const Posts = defineCollection({
|
|
43
|
+
slug: "posts",
|
|
44
|
+
labels: { singular: "Post", plural: "Posts" },
|
|
45
|
+
fields: [
|
|
46
|
+
text({ name: "title", required: true }),
|
|
47
|
+
text({ name: "slug", required: true, unique: true }),
|
|
48
|
+
textarea({ name: "excerpt" }),
|
|
49
|
+
richText({ name: "content" }),
|
|
50
|
+
date({ name: "publishedAt" }),
|
|
51
|
+
select({
|
|
52
|
+
name: "category",
|
|
53
|
+
options: [
|
|
54
|
+
{ label: "News", value: "news" },
|
|
55
|
+
{ label: "Guide", value: "guide" },
|
|
56
|
+
],
|
|
57
|
+
}),
|
|
58
|
+
],
|
|
59
|
+
// Draft and Published, with Save draft and Publish buttons in the admin.
|
|
60
|
+
status: true,
|
|
61
|
+
admin: { useAsTitle: "title" },
|
|
62
|
+
access: {
|
|
63
|
+
read: () => true,
|
|
64
|
+
create: ({ user }) => Boolean(user),
|
|
65
|
+
update: ({ user }) => Boolean(user),
|
|
66
|
+
delete: ({ roles }) => roles.includes("admin"),
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Rules that matter:
|
|
72
|
+
|
|
73
|
+
- Import from `nextly/config` in collection files and in `nextly.config.ts`.
|
|
74
|
+
- Use **relative imports** in these files, never the `@/` alias. The Nextly
|
|
75
|
+
CLI loads them with Node.js, which does not know Next.js aliases.
|
|
76
|
+
- `slug` is the table and API name: lowercase, stable. Renaming it later is a
|
|
77
|
+
migration, not an edit.
|
|
78
|
+
- `id`, `createdAt` and `updatedAt` are added for you; do not declare them.
|
|
79
|
+
Declare `title` and `slug` yourself, as above, or the admin shows no input
|
|
80
|
+
for them and the slug is not unique. The admin fills an empty `slug` from
|
|
81
|
+
the title.
|
|
82
|
+
- Field helpers in this release: `text`, `textarea`, `richText`, `email`,
|
|
83
|
+
`password`, `code`, `number`, `checkbox`, `date`, `select`, `radio`,
|
|
84
|
+
`chips`, `upload`, `relationship`, `repeater`, `array`, `group`, `json`.
|
|
85
|
+
`relationship({ name: "author", relationTo: "users" })` links entries;
|
|
86
|
+
add `hasMany: true` for several. `upload({ name: "image", relationTo: "media" })`
|
|
87
|
+
links a media file.
|
|
88
|
+
- Access rules are explained in the `nextly-authentication` skill.
|
|
89
|
+
|
|
90
|
+
## 2. Register it
|
|
91
|
+
|
|
92
|
+
`nextly.config.ts`:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { defineConfig } from "nextly/config";
|
|
96
|
+
|
|
97
|
+
import { Posts } from "./src/collections/posts";
|
|
98
|
+
|
|
99
|
+
export default defineConfig({
|
|
100
|
+
collections: [Posts],
|
|
101
|
+
singles: [],
|
|
102
|
+
typescript: {
|
|
103
|
+
outputFile: "./src/types/generated/nextly-types.ts",
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## 3. Apply it in development
|
|
109
|
+
|
|
110
|
+
Restart `next dev` after adding or changing a collection. The change is
|
|
111
|
+
applied to the development database on the first request after the restart:
|
|
112
|
+
the log says `Code-first sync completed`. On Next.js 16.4 the in-process
|
|
113
|
+
reload does not pick up a changed field without a restart.
|
|
114
|
+
|
|
115
|
+
Then open `/admin`: the collection is in the sidebar, with list and create
|
|
116
|
+
screens.
|
|
117
|
+
|
|
118
|
+
## 4. Generate types
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
npx nextly generate:types
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
This writes the file named in `typescript.outputFile`, and Zod schemas under
|
|
125
|
+
`src/db/schemas/collections/zod/` (`db/schemas/collections/zod/` without a
|
|
126
|
+
`src/` folder). With them, `find({ collection: "posts" })` is typed; without
|
|
127
|
+
them, entries are loosely typed and fields read as `unknown`. Regenerate in
|
|
128
|
+
the same change as every schema edit.
|
|
129
|
+
|
|
130
|
+
The Zod schemas import `z` from `nextly/zod`, so the project needs no `zod`
|
|
131
|
+
dependency of its own. To combine them with your own Zod code, import `z`
|
|
132
|
+
from `nextly/zod` there too.
|
|
133
|
+
|
|
134
|
+
## 5. Read entries in a page
|
|
135
|
+
|
|
136
|
+
Content is read on the server with the Direct API. Do not build an API route
|
|
137
|
+
to fetch your own content.
|
|
138
|
+
|
|
139
|
+
```tsx
|
|
140
|
+
// src/app/posts/page.tsx
|
|
141
|
+
import { getNextly } from "nextly";
|
|
142
|
+
import { cachedFind, nextlyTags } from "nextly/runtime";
|
|
143
|
+
|
|
144
|
+
import nextlyConfig from "../../../nextly.config";
|
|
145
|
+
|
|
146
|
+
async function getPublishedPosts() {
|
|
147
|
+
return cachedFind(
|
|
148
|
+
async () => {
|
|
149
|
+
const nextly = await getNextly({ config: nextlyConfig });
|
|
150
|
+
const { items } = await nextly.find({
|
|
151
|
+
collection: "posts",
|
|
152
|
+
status: "published",
|
|
153
|
+
limit: 10,
|
|
154
|
+
});
|
|
155
|
+
return items;
|
|
156
|
+
},
|
|
157
|
+
{ tags: nextlyTags("posts"), keyParts: ["posts", "list"] }
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export default async function PostsPage() {
|
|
162
|
+
const posts = await getPublishedPosts();
|
|
163
|
+
return (
|
|
164
|
+
<ul>
|
|
165
|
+
{posts.map(post => (
|
|
166
|
+
<li key={String(post.id)}>{String(post.title)}</li>
|
|
167
|
+
))}
|
|
168
|
+
</ul>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
- Without a `src/` folder the page is `app/posts/page.tsx`, importing
|
|
174
|
+
`"../../nextly.config"`.
|
|
175
|
+
- `find()` returns `{ items, meta }`. `meta` holds the totals and pages.
|
|
176
|
+
- `status: "published"` keeps drafts off the page. Without it a trusted
|
|
177
|
+
server read returns drafts too.
|
|
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`.
|
|
182
|
+
- `keyParts` names the cache entry, and everyone whose read uses the same key
|
|
183
|
+
gets the same rows. Cache only reads that are the same for every reader:
|
|
184
|
+
published content of a collection with no read rule, on the collection or
|
|
185
|
+
on any of its fields. Do not cache a read that applies a reader's access
|
|
186
|
+
rules (`overrideAccess: false` with a `user`): the reader's roles and
|
|
187
|
+
permissions can change without any content changing, so no tag clears the
|
|
188
|
+
entry. Read it uncached inside `<Suspense>` instead.
|
|
189
|
+
- Rich text comes back as Lexical JSON. Pass `richTextFormat: "html"` to
|
|
190
|
+
`find()` to get HTML instead.
|
|
191
|
+
- Users are not a normal collection: read them with `nextly.users.find()`,
|
|
192
|
+
which supports pagination and `search` but not `where`.
|
|
193
|
+
|
|
194
|
+
## 6. Make a migration before deploying
|
|
195
|
+
|
|
196
|
+
Development applies changes directly; production applies migration files.
|
|
197
|
+
When the change is ready to ship:
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
npx nextly migrate:create add_posts
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
This compares the config with the last migration's snapshot and writes
|
|
204
|
+
`src/db/migrations/<timestamp>_add_posts.sql` plus a snapshot under
|
|
205
|
+
`src/db/migrations/meta/`. Read the SQL, then commit both with the config
|
|
206
|
+
change. `npx nextly migrate` applies pending migrations; the `nextly-deploy`
|
|
207
|
+
skill runs it on every deploy. Once a migration is shared, never edit it: add
|
|
208
|
+
a new one.
|
|
209
|
+
|
|
210
|
+
## Check it worked
|
|
211
|
+
|
|
212
|
+
- The admin lists the collection and saves an entry.
|
|
213
|
+
- `npx nextly generate:types` succeeds and `npx tsc --noEmit` is clean.
|
|
214
|
+
- The page shows a published entry, and a draft does not appear.
|
|
215
|
+
- `next build` succeeds.
|