create-nextly-app 0.0.2-alpha.6 → 0.0.2-alpha.62
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/{chunk-6NVRVAZA.mjs → chunk-CTBK76CF.mjs} +2727 -1726
- package/dist/chunk-CTBK76CF.mjs.map +1 -0
- package/dist/cli.cjs +2831 -1831
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.mjs +33 -35
- package/dist/cli.mjs.map +1 -1
- package/dist/index.cjs +2646 -1643
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.mjs +1 -1
- package/package.json +13 -12
- package/templates/base/.env.example +11 -0
- package/templates/base/AGENTS.md.template +158 -0
- package/templates/base/CLAUDE.md.template +1 -0
- package/templates/base/gitignore +42 -0
- package/templates/base/next.config.ts +11 -17
- package/templates/base/src/app/admin/api/media/[[...path]]/route.ts +26 -0
- package/templates/base/src/app/globals.css +15 -2
- package/templates/base/src/app/layout.tsx +30 -15
- package/templates/base/tsconfig.json +8 -1
- package/templates/blank/src/app/globals.css +37 -1
- package/templates/blank/src/app/layout.tsx +26 -15
- package/templates/plugin/AGENTS.md.template +96 -0
- package/templates/plugin/CLAUDE.md.template +1 -0
- package/templates/plugin/README.md +44 -0
- package/templates/plugin/dev/.env.example +8 -0
- package/templates/plugin/dev/instrumentation.ts +10 -0
- package/templates/plugin/dev/next.config.ts +30 -0
- package/templates/plugin/dev/nextly.config.ts +17 -0
- package/templates/plugin/dev/src/app/admin/[[...params]]/page.tsx +9 -0
- package/templates/plugin/dev/src/app/admin/api/[[...params]]/route.ts +19 -0
- package/templates/plugin/dev/src/app/layout.tsx +13 -0
- package/templates/plugin/dev/tsconfig.json +22 -0
- package/templates/plugin/eslint.config.mjs +19 -0
- package/templates/plugin/gitignore +10 -0
- package/templates/plugin/src/admin/SettingsPage.tsx +57 -0
- package/templates/plugin/src/admin/index.ts +24 -0
- package/templates/plugin/src/collections/example.ts +13 -0
- package/templates/plugin/src/index.ts +6 -0
- package/templates/plugin/src/plugin.test.ts +28 -0
- package/templates/plugin/src/plugin.ts +48 -0
- package/templates/plugin/src/types.ts +17 -0
- package/templates/plugin/template.json +14 -0
- package/templates/plugin/tsconfig.json +18 -0
- package/templates/plugin/tsup.config.ts +24 -0
- package/templates/plugin/vitest.config.ts +8 -0
- package/dist/chunk-6NVRVAZA.mjs.map +0 -1
- package/templates/blank/src/access/README.md +0 -22
- package/templates/blank/src/collections/README.md +0 -22
- package/templates/blank/src/lib/README.md +0 -15
- package/templates/blank/src/singles/README.md +0 -19
- /package/templates/blank/src/{components → app/(frontend)/components}/README.md +0 -0
- /package/templates/blank/src/{components → app/(frontend)/components}/ThemeToggle.tsx +0 -0
package/dist/index.d.cts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Project type / template selection.
|
|
3
3
|
* "blog" is the first content template added for alpha.
|
|
4
|
+
* "plugin" scaffolds a publishable Nextly plugin package + embedded /dev playground (D44/D45).
|
|
4
5
|
*/
|
|
5
|
-
type ProjectType = "blank" | "blog";
|
|
6
|
+
type ProjectType = "blank" | "blog" | "plugin";
|
|
6
7
|
/**
|
|
7
8
|
* Schema approach selection for content templates.
|
|
8
9
|
* - code-first: Full schema definitions in nextly.config.ts (like Payload CMS)
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Project type / template selection.
|
|
3
3
|
* "blog" is the first content template added for alpha.
|
|
4
|
+
* "plugin" scaffolds a publishable Nextly plugin package + embedded /dev playground (D44/D45).
|
|
4
5
|
*/
|
|
5
|
-
type ProjectType = "blank" | "blog";
|
|
6
|
+
type ProjectType = "blank" | "blog" | "plugin";
|
|
6
7
|
/**
|
|
7
8
|
* Schema approach selection for content templates.
|
|
8
9
|
* - code-first: Full schema definitions in nextly.config.ts (like Payload CMS)
|
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.62",
|
|
4
4
|
"description": "CLI to scaffold Nextly in your Next.js project",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -27,27 +27,28 @@
|
|
|
27
27
|
"commander": "^14.0.2",
|
|
28
28
|
"execa": "^9.6.0",
|
|
29
29
|
"fs-extra": "^11.3.0",
|
|
30
|
-
"ora": "^9.0.0",
|
|
31
30
|
"picocolors": "^1.1.1",
|
|
32
|
-
"tar": "^7.
|
|
31
|
+
"tar": "^7.5.19"
|
|
33
32
|
},
|
|
34
33
|
"devDependencies": {
|
|
35
34
|
"@types/fs-extra": "^11.0.4",
|
|
36
|
-
"@types/tar": "^6.1.13",
|
|
37
35
|
"@types/node": "^20.19.17",
|
|
38
|
-
"@
|
|
39
|
-
"@
|
|
40
|
-
"
|
|
36
|
+
"@types/tar": "^6.1.13",
|
|
37
|
+
"@typescript-eslint/parser": "^8.59.0",
|
|
38
|
+
"@vitest/coverage-v8": "^4.1.0",
|
|
39
|
+
"@vitest/ui": "^4.1.0",
|
|
40
|
+
"eslint": "^9.39.1",
|
|
41
41
|
"tsup": "^8.5.0",
|
|
42
42
|
"typescript": "^5.9.3",
|
|
43
43
|
"vite-tsconfig-paths": "^5.1.4",
|
|
44
|
-
"vitest": "^4.0
|
|
45
|
-
"@nextlyhq/eslint-config": "0.0.2-alpha.
|
|
46
|
-
"@nextlyhq/
|
|
47
|
-
"@nextlyhq/
|
|
44
|
+
"vitest": "^4.1.0",
|
|
45
|
+
"@nextlyhq/eslint-config": "0.0.2-alpha.62",
|
|
46
|
+
"@nextlyhq/eslint-plugin": "0.0.2-alpha.62",
|
|
47
|
+
"@nextlyhq/telemetry": "0.0.2-alpha.62",
|
|
48
|
+
"@nextlyhq/tsconfig": "0.0.2-alpha.62"
|
|
48
49
|
},
|
|
49
50
|
"engines": {
|
|
50
|
-
"node": "
|
|
51
|
+
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
|
51
52
|
},
|
|
52
53
|
"keywords": [
|
|
53
54
|
"nextly",
|
|
@@ -12,5 +12,16 @@ NEXTLY_SECRET=change-me-generate-a-secure-secret
|
|
|
12
12
|
# Application URL
|
|
13
13
|
NEXT_PUBLIC_APP_URL=http://localhost:3000
|
|
14
14
|
|
|
15
|
+
# Development diagnostics (opt-in)
|
|
16
|
+
# Uncomment to add a _devDiagnostics field to error responses, carrying the
|
|
17
|
+
# error's log context and the underlying cause, so a failure names itself while
|
|
18
|
+
# you build instead of only in the server log.
|
|
19
|
+
#
|
|
20
|
+
# Left commented on purpose. It is the SECOND of two independent signals — the
|
|
21
|
+
# first is NODE_ENV — and the second exists precisely because NODE_ENV is a
|
|
22
|
+
# runtime value a deployment can carry by mistake. A default that ships in this
|
|
23
|
+
# file would be true in exactly that case, which is the one it guards against.
|
|
24
|
+
# NEXTLY_DEV_DIAGNOSTICS=1
|
|
25
|
+
|
|
15
26
|
# Storage Configuration
|
|
16
27
|
{{storageEnvVars}}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
<!-- nextly:managed:start -->
|
|
2
|
+
<!--
|
|
3
|
+
Everything between these markers is generated by create-nextly-app and may be
|
|
4
|
+
replaced when the project is upgraded. Write your own notes OUTSIDE the block:
|
|
5
|
+
anything above or below it is yours and is preserved.
|
|
6
|
+
-->
|
|
7
|
+
|
|
8
|
+
# Agent guide for this Nextly project
|
|
9
|
+
|
|
10
|
+
A Next.js app with [Nextly](https://nextlyhq.com) for content. The CMS runs
|
|
11
|
+
inside this app rather than beside it: same server, same database, no separate
|
|
12
|
+
service to start.
|
|
13
|
+
|
|
14
|
+
## Where things are
|
|
15
|
+
|
|
16
|
+
| path | what it is |
|
|
17
|
+
| ---------------------- | ------------------------------------------------------------------ |
|
|
18
|
+
| `nextly.config.ts` | the config the whole app boots from — collections, database, admin |
|
|
19
|
+
| `src/collections/` | code-first collection definitions, when this project uses them |
|
|
20
|
+
| `src/app/(frontend)/` | the public site |
|
|
21
|
+
| `src/app/admin/` | the admin panel, served at `/admin` |
|
|
22
|
+
| `migrations/` | SQL migrations, in this project's dialect |
|
|
23
|
+
| `src/types/generated/` | generated types — never edit by hand |
|
|
24
|
+
|
|
25
|
+
## Commands
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
{{runCommand}} dev # start Next.js; schema changes apply in-process
|
|
29
|
+
{{runCommand}} build # migrate, then build
|
|
30
|
+
{{runCommand}} db:setup # first run: create the schema
|
|
31
|
+
{{runCommand}} db:migrate # apply pending migrations
|
|
32
|
+
{{runCommand}} db:migrate:status # what has and has not been applied
|
|
33
|
+
{{runCommand}} db:migrate:fresh # drop everything and re-run (destructive)
|
|
34
|
+
{{runCommand}} types:generate # regenerate types after a schema change
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
This project uses **{{databaseDialect}}**.
|
|
38
|
+
|
|
39
|
+
## Reading content
|
|
40
|
+
|
|
41
|
+
Content is read through the Direct API, on the server:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { getNextly } from "nextly";
|
|
45
|
+
import nextlyConfig from "@nextly-config";
|
|
46
|
+
|
|
47
|
+
const nextly = await getNextly({ config: nextlyConfig });
|
|
48
|
+
const posts = await nextly.find({ collection: "posts", limit: 10 });
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**`find()` returns loosely-typed documents until types are generated.** Without
|
|
52
|
+
`{{runCommand}} types:generate`, `posts.items` is `Record<string, unknown>[]` and a
|
|
53
|
+
field access is `unknown`. Generating types makes the slug and the return type
|
|
54
|
+
infer, so `posts.items` becomes `Post[]`.
|
|
55
|
+
|
|
56
|
+
Until then, narrow with a runtime check rather than an assertion — a document
|
|
57
|
+
missing a field yields `undefined`, and asserting the type only moves the
|
|
58
|
+
failure to where the value is used:
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
const slug = typeof doc.slug === "string" ? doc.slug : undefined;
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Things that surprise people
|
|
65
|
+
|
|
66
|
+
- **Users are not a normal collection.** Read them through `nextly.users`, not
|
|
67
|
+
`find({ collection: "users" })`. The users namespace supports pagination and
|
|
68
|
+
`search`; it does not support a `where` filter.
|
|
69
|
+
- **A migration you generate is one file, in THIS project's dialect.**
|
|
70
|
+
`{{execCommand}} nextly migrate:create <name>` (no package.json script exists for it) writes a
|
|
71
|
+
single `migrations/<timestamp>_<slug>.sql` containing **{{databaseDialect}}**
|
|
72
|
+
SQL — no `.mysql.sql` or `.sqlite.sql` beside it. Edit that file.
|
|
73
|
+
- **A suffixed set means the migration was shipped, not generated.** A template
|
|
74
|
+
that supports every dialect ships `0001_x.sql` (Postgres) alongside
|
|
75
|
+
`0001_x.mysql.sql` and `0001_x.sqlite.sql`, and the CLI runs the variant for
|
|
76
|
+
the configured dialect. Those are prebuilt. Adding companion variants to a
|
|
77
|
+
migration you generated is not part of the normal workflow, and nothing will
|
|
78
|
+
run them unless this project is itself meant to support several dialects.
|
|
79
|
+
- **The admin needs a first user.** Visit `/admin` after `{{runCommand}} dev` and
|
|
80
|
+
complete the setup screen.
|
|
81
|
+
- **Cache tags come from `nextly/runtime`.** Wrap PUBLIC reads in `cachedFind`
|
|
82
|
+
and tag them with `nextlyTags(collection)` so a write invalidates the right
|
|
83
|
+
pages.
|
|
84
|
+
|
|
85
|
+
**`keyParts` is a security boundary, not a performance detail.** Two callers
|
|
86
|
+
whose `keyParts` match share one cache entry. So if the read applies any
|
|
87
|
+
per-caller rule — owner-only scoping, role-based visibility, an API key's
|
|
88
|
+
narrowed scope — the caller's identity has to be IN the key, or one user's
|
|
89
|
+
rows get served to another:
|
|
90
|
+
|
|
91
|
+
**A shared cache entry is only safe if the read is public for EVERY reader.**
|
|
92
|
+
Two independent things can make it not so, and each needs its own answer:
|
|
93
|
+
|
|
94
|
+
- **drafts** — `find()` defaults to `overrideAccess: true`, a trusted server
|
|
95
|
+
read, so a slug filter alone can return an unpublished row. `status:
|
|
96
|
+
"published"` is enforced even on a trusted read and fixes this one.
|
|
97
|
+
- **per-row access rules** — owner-only scoping, role-based visibility, an API
|
|
98
|
+
key's narrowed scope. `status` does NOT evaluate these; it is a
|
|
99
|
+
draft/published lifecycle filter and nothing more. A published row that only
|
|
100
|
+
its owner may read is still returned by the query above.
|
|
101
|
+
|
|
102
|
+
So the decision is: **does this collection carry read-access rules?**
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
// NO access rules — the result is identical for every reader, so one shared
|
|
106
|
+
// entry is correct and gets the full ISR benefit.
|
|
107
|
+
// `find()` always returns the list envelope `{ items, meta }`, so a detail route takes the
|
|
108
|
+
// first item — and caches THAT, not the envelope around it.
|
|
109
|
+
cachedFind(
|
|
110
|
+
async () =>
|
|
111
|
+
(
|
|
112
|
+
await nextly.find({
|
|
113
|
+
collection: "posts",
|
|
114
|
+
where: { slug: { equals: slug } },
|
|
115
|
+
status: "published",
|
|
116
|
+
limit: 1,
|
|
117
|
+
})
|
|
118
|
+
).items[0] ?? null,
|
|
119
|
+
{ tags: nextlyTags("posts"), keyParts: ["posts", slug] }
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
// ACCESS RULES — evaluate them as the caller, and key by EVERY dimension the
|
|
123
|
+
// rules read, not just who the caller is.
|
|
124
|
+
//
|
|
125
|
+
// Both role fields are optional and Nextly reads BOTH: the many-to-many
|
|
126
|
+
// `roles` unioned with the singular `role`. Deduped and sorted, so one role
|
|
127
|
+
// set spelled two ways is one cache entry rather than two.
|
|
128
|
+
const roleKey = (u: { role?: string; roles?: string[] }) =>
|
|
129
|
+
[...new Set([...(u.roles ?? []), u.role ?? []].flat())].sort().join(",");
|
|
130
|
+
|
|
131
|
+
cachedFind(
|
|
132
|
+
() => nextly.find({ collection: "orders", user, overrideAccess: false }),
|
|
133
|
+
{
|
|
134
|
+
tags: nextlyTags("orders"),
|
|
135
|
+
keyParts: ["orders", "list", user.id, roleKey(user)],
|
|
136
|
+
}
|
|
137
|
+
);
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**`user.id` alone is not the authorization context.** Roles change, custom
|
|
141
|
+
claims change, an API key's scope is narrower than its owner's. A key built
|
|
142
|
+
from identity alone survives all of those, so the same person can fill the
|
|
143
|
+
entry while privileged and read it back after being downgraded — the tags bust
|
|
144
|
+
on CONTENT changes, and a permission change is not one. Include whatever the
|
|
145
|
+
rules actually read: the role set, the claim, the key's scope.
|
|
146
|
+
|
|
147
|
+
If you are unsure whether a collection has read rules, do not cache it. A
|
|
148
|
+
missing cache is slow; a shared one leaks.
|
|
149
|
+
|
|
150
|
+
## Conventions worth keeping
|
|
151
|
+
|
|
152
|
+
- Server components read content directly; do not build an API route to fetch
|
|
153
|
+
your own content.
|
|
154
|
+
- Regenerate types in the same change as the schema edit that needs them.
|
|
155
|
+
- Migrations are append-only once shared. Add a new one rather than editing an
|
|
156
|
+
applied file.
|
|
157
|
+
|
|
158
|
+
<!-- nextly:managed:end -->
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@AGENTS.md
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
|
2
|
+
|
|
3
|
+
# dependencies
|
|
4
|
+
/node_modules
|
|
5
|
+
/.pnp
|
|
6
|
+
.pnp.*
|
|
7
|
+
.yarn/*
|
|
8
|
+
!.yarn/patches
|
|
9
|
+
!.yarn/plugins
|
|
10
|
+
!.yarn/releases
|
|
11
|
+
!.yarn/versions
|
|
12
|
+
|
|
13
|
+
# testing
|
|
14
|
+
/coverage
|
|
15
|
+
|
|
16
|
+
# next.js
|
|
17
|
+
/.next/
|
|
18
|
+
/out/
|
|
19
|
+
|
|
20
|
+
# production
|
|
21
|
+
/build
|
|
22
|
+
|
|
23
|
+
# misc
|
|
24
|
+
.DS_Store
|
|
25
|
+
*.pem
|
|
26
|
+
|
|
27
|
+
# debug
|
|
28
|
+
npm-debug.log*
|
|
29
|
+
yarn-debug.log*
|
|
30
|
+
yarn-error.log*
|
|
31
|
+
.pnpm-debug.log*
|
|
32
|
+
|
|
33
|
+
# env files (can opt-in for committing if needed)
|
|
34
|
+
.env*
|
|
35
|
+
!.env.example
|
|
36
|
+
|
|
37
|
+
# vercel
|
|
38
|
+
.vercel
|
|
39
|
+
|
|
40
|
+
# typescript
|
|
41
|
+
*.tsbuildinfo
|
|
42
|
+
next-env.d.ts
|
|
@@ -1,21 +1,15 @@
|
|
|
1
1
|
import type { NextConfig } from "next";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"better-sqlite3",
|
|
15
|
-
"bcryptjs",
|
|
16
|
-
"sharp",
|
|
17
|
-
"esbuild",
|
|
18
|
-
],
|
|
19
|
-
};
|
|
3
|
+
/**
|
|
4
|
+
* Minimal base `next.config.ts` for bundled templates.
|
|
5
|
+
*
|
|
6
|
+
* This file is intentionally left minimal (no adapter whitelist). The
|
|
7
|
+
* scaffolder will generate or patch a database-specific `next.config.ts`
|
|
8
|
+
* that includes only the selected adapter and driver in
|
|
9
|
+
* `serverExternalPackages`. Keeping the base file minimal avoids
|
|
10
|
+
* advertising adapters the user didn't choose and prevents misleading
|
|
11
|
+
* generated projects.
|
|
12
|
+
*/
|
|
13
|
+
const nextConfig: NextConfig = {};
|
|
20
14
|
|
|
21
15
|
export default nextConfig;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin Media API — permission-gated management surface.
|
|
3
|
+
*
|
|
4
|
+
* The admin's session cookie is scoped to `/admin`, so it reaches here but not
|
|
5
|
+
* the public `/api/media`. Every operation is checked against a media
|
|
6
|
+
* permission (read/create/update/delete-media) and the acting identity is
|
|
7
|
+
* taken from the session/API key, never a request field. This is the only
|
|
8
|
+
* place media can be uploaded, edited, moved or deleted.
|
|
9
|
+
*
|
|
10
|
+
* Supported (all gated):
|
|
11
|
+
* - GET/POST/PATCH/DELETE /admin/api/media/* (files, folders, bulk delete)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { createMediaHandlers } from "nextly/api/media-handlers";
|
|
15
|
+
|
|
16
|
+
import config from "@nextly-config";
|
|
17
|
+
|
|
18
|
+
const handlers = createMediaHandlers({
|
|
19
|
+
config,
|
|
20
|
+
requireAuth: true,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export const GET = handlers.GET;
|
|
24
|
+
export const POST = handlers.POST;
|
|
25
|
+
export const PATCH = handlers.PATCH;
|
|
26
|
+
export const DELETE = handlers.DELETE;
|
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
@import "tailwindcss";
|
|
2
2
|
|
|
3
|
+
/* The families the imported font stylesheets declare, bound to the variables the rules
|
|
4
|
+
below read. Named here rather than by `next/font`, which used to inject them: the faces
|
|
5
|
+
now arrive as ordinary stylesheets, so the binding has to be written down somewhere, and
|
|
6
|
+
the name is load-bearing — the admin theme reads `--font-geist`. Quoted family names, so
|
|
7
|
+
the two-word values stay one identifier. */
|
|
8
|
+
:root {
|
|
9
|
+
--font-geist: "Geist Variable";
|
|
10
|
+
--font-geist-mono: "Geist Mono Variable";
|
|
11
|
+
}
|
|
12
|
+
|
|
3
13
|
:root {
|
|
4
14
|
--background: #ffffff;
|
|
5
15
|
--foreground: #171717;
|
|
@@ -8,8 +18,11 @@
|
|
|
8
18
|
@theme inline {
|
|
9
19
|
--color-background: var(--background);
|
|
10
20
|
--color-foreground: var(--foreground);
|
|
11
|
-
|
|
12
|
-
|
|
21
|
+
/* Fallback arguments, not a trailing family list: an unresolved var() with no
|
|
22
|
+
fallback invalidates the whole declaration rather than falling through to
|
|
23
|
+
what comes after the closing paren. */
|
|
24
|
+
--font-sans: var(--font-geist, ui-sans-serif), system-ui, sans-serif;
|
|
25
|
+
--font-mono: var(--font-geist-mono, ui-monospace), monospace;
|
|
13
26
|
}
|
|
14
27
|
|
|
15
28
|
@media (prefers-color-scheme: dark) {
|
|
@@ -1,16 +1,31 @@
|
|
|
1
1
|
import type { Metadata } from "next";
|
|
2
|
-
import
|
|
2
|
+
import "@fontsource-variable/geist";
|
|
3
|
+
import "@fontsource-variable/geist-mono";
|
|
3
4
|
import "./globals.css";
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
/**
|
|
7
|
+
* The faces are imported as STYLESHEETS from packages in `node_modules`, rather
|
|
8
|
+
* than fetched from fonts.googleapis.com by `next/font/google` while
|
|
9
|
+
* `next build` runs — which made every build depend on reaching a third party
|
|
10
|
+
* and fail behind a proxy, on a locked-down runner, or offline.
|
|
11
|
+
*
|
|
12
|
+
* A bare package import rather than `next/font/local` pointing INTO
|
|
13
|
+
* `node_modules`: a literal path asserts where the package physically lives,
|
|
14
|
+
* and that assertion is false under Yarn's Plug'n'Play linker (no
|
|
15
|
+
* `node_modules` at all), under npm/Yarn workspace hoisting (the package moves
|
|
16
|
+
* to the workspace root), and under pnpm's own symlinked store. An import asks
|
|
17
|
+
* the resolver instead, which is correct under every layout.
|
|
18
|
+
*
|
|
19
|
+
* The trade is `next/font`'s metric-adjusted fallback, so text can shift
|
|
20
|
+
* slightly as the face arrives. The families are bound to this app's `--font-*`
|
|
21
|
+
* variables in `globals.css`, which is what every rule downstream reads. *
|
|
22
|
+
* Importing the package root ships every subset the face offers — Latin, Latin
|
|
23
|
+
* Extended, Cyrillic, Vietnamese — but each `@font-face` carries a
|
|
24
|
+
* `unicode-range`, so a browser downloads only the subsets the page actually
|
|
25
|
+
* uses. The deployed bundle is larger than a single hand-picked file; what a
|
|
26
|
+
* reader fetches is not, and a page in Cyrillic now gets its face instead of a
|
|
27
|
+
* fallback.
|
|
28
|
+
*/
|
|
14
29
|
|
|
15
30
|
/**
|
|
16
31
|
* `metadataBase` tells Next.js how to resolve relative URLs in OpenGraph
|
|
@@ -32,12 +47,12 @@ export default function RootLayout({
|
|
|
32
47
|
children: React.ReactNode;
|
|
33
48
|
}>) {
|
|
34
49
|
return (
|
|
50
|
+
// The font variables belong on <html>: the admin theme declares --font-sans
|
|
51
|
+
// in a non-inline @theme, which emits it into :root and resolves the
|
|
52
|
+
// reference THERE. A variable exposed lower down, on <body>, is invisible to
|
|
53
|
+
// that declaration however it is spelled.
|
|
35
54
|
<html lang="en" suppressHydrationWarning>
|
|
36
|
-
<body
|
|
37
|
-
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
|
38
|
-
>
|
|
39
|
-
{children}
|
|
40
|
-
</body>
|
|
55
|
+
<body className="antialiased">{children}</body>
|
|
41
56
|
</html>
|
|
42
57
|
);
|
|
43
58
|
}
|
|
@@ -20,7 +20,14 @@
|
|
|
20
20
|
],
|
|
21
21
|
"paths": {
|
|
22
22
|
"@/*": ["./src/*"],
|
|
23
|
-
"
|
|
23
|
+
"@/components/*": ["./src/app/(frontend)/components/*"],
|
|
24
|
+
"@nextly-config": ["./nextly.config.ts"],
|
|
25
|
+
// Pagefind's browser bundle is generated into public/ at build time, so it
|
|
26
|
+
// is imported by URL and cannot be resolved on disk while type-checking.
|
|
27
|
+
// A `declare module` cannot describe it either: TypeScript treats a rooted
|
|
28
|
+
// specifier as relative and rejects the declaration (TS2436). Templates
|
|
29
|
+
// without a search page simply never import it, leaving this mapping inert.
|
|
30
|
+
"/pagefind/pagefind.js": ["./src/types/pagefind.d.ts"]
|
|
24
31
|
}
|
|
25
32
|
},
|
|
26
33
|
"include": [
|
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
@import "tailwindcss";
|
|
2
2
|
|
|
3
|
+
/* The families the imported font stylesheets declare, bound to the variables the rules
|
|
4
|
+
below read. Named here rather than by `next/font`, which used to inject them: the faces
|
|
5
|
+
now arrive as ordinary stylesheets, so the binding has to be written down somewhere, and
|
|
6
|
+
the name is load-bearing — the admin theme reads `--font-geist`. Quoted family names, so
|
|
7
|
+
the two-word values stay one identifier. */
|
|
8
|
+
:root {
|
|
9
|
+
/* `--font-geist` and `--font-geist-mono` are the names the ADMIN theme reads
|
|
10
|
+
(`--font-sans: var(--font-geist, Geist), ...` in a non-inline `@theme`, so
|
|
11
|
+
it resolves at `:root`). A template that loads a different face still has to
|
|
12
|
+
expose it under these names or the admin renders in a system fallback while
|
|
13
|
+
the app's own pages render correctly. */
|
|
14
|
+
--font-geist: "Inter Variable";
|
|
15
|
+
--font-geist-mono: "JetBrains Mono Variable";
|
|
16
|
+
/* This template's own display face, read by `.font-display` below. NOT named
|
|
17
|
+
`--font-mono` for its mono face: that is the theme's OWN token, declared at
|
|
18
|
+
`:root`, and redefining it here replaces the theme's whole fallback chain
|
|
19
|
+
rather than feeding it. */
|
|
20
|
+
--font-display: "Inter Variable";
|
|
21
|
+
}
|
|
22
|
+
|
|
3
23
|
/**
|
|
4
24
|
* Blank-template global styles.
|
|
5
25
|
*
|
|
@@ -28,6 +48,18 @@
|
|
|
28
48
|
--color-foreground: #f9fafb;
|
|
29
49
|
}
|
|
30
50
|
|
|
51
|
+
/* `@theme inline` on purpose, and it is the whole point of this block. An inline theme
|
|
52
|
+
SUBSTITUTES the value where the utility is used; a plain `@theme` emits the variable into
|
|
53
|
+
`:root`, which is where `@nextlyhq/admin` declares its own `--font-mono` — redefining it there
|
|
54
|
+
would replace the admin's entire fallback chain with this one family.
|
|
55
|
+
|
|
56
|
+
Needed because the frontend routes do not load the admin stylesheet, so nothing else binds the
|
|
57
|
+
`font-mono` utility this template uses on its badge and file-path text; without it they fall
|
|
58
|
+
back to Tailwind's default mono rather than the face the template loads. */
|
|
59
|
+
@theme inline {
|
|
60
|
+
--font-mono: var(--font-geist-mono, ui-monospace), monospace;
|
|
61
|
+
}
|
|
62
|
+
|
|
31
63
|
@theme {
|
|
32
64
|
--color-background: var(--color-background);
|
|
33
65
|
--color-foreground: var(--color-foreground);
|
|
@@ -67,7 +99,11 @@ body {
|
|
|
67
99
|
}
|
|
68
100
|
|
|
69
101
|
.font-display {
|
|
70
|
-
|
|
102
|
+
/* The generic families are the var()'s fallback argument rather than a list
|
|
103
|
+
after it: an unresolved var() with no fallback invalidates the whole
|
|
104
|
+
declaration, so the element would inherit its parent's face instead of
|
|
105
|
+
falling through to these. */
|
|
106
|
+
font-family: var(--font-display, ui-sans-serif), system-ui, sans-serif;
|
|
71
107
|
}
|
|
72
108
|
|
|
73
109
|
.btn-primary {
|
|
@@ -1,19 +1,32 @@
|
|
|
1
1
|
import type { Metadata } from "next";
|
|
2
|
-
import
|
|
2
|
+
import "@fontsource-variable/inter";
|
|
3
|
+
import "@fontsource-variable/jetbrains-mono";
|
|
3
4
|
|
|
4
5
|
import "./globals.css";
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
7
|
+
/**
|
|
8
|
+
* The faces are imported as STYLESHEETS from packages in `node_modules`, rather
|
|
9
|
+
* than fetched from fonts.googleapis.com by `next/font/google` while
|
|
10
|
+
* `next build` runs — which made every build depend on reaching a third party
|
|
11
|
+
* and fail behind a proxy, on a locked-down runner, or offline.
|
|
12
|
+
*
|
|
13
|
+
* A bare package import rather than `next/font/local` pointing INTO
|
|
14
|
+
* `node_modules`: a literal path asserts where the package physically lives,
|
|
15
|
+
* and that assertion is false under Yarn's Plug'n'Play linker (no
|
|
16
|
+
* `node_modules` at all), under npm/Yarn workspace hoisting (the package moves
|
|
17
|
+
* to the workspace root), and under pnpm's own symlinked store. An import asks
|
|
18
|
+
* the resolver instead, which is correct under every layout.
|
|
19
|
+
*
|
|
20
|
+
* The trade is `next/font`'s metric-adjusted fallback, so text can shift
|
|
21
|
+
* slightly as the face arrives. The families are bound to this app's `--font-*`
|
|
22
|
+
* variables in `globals.css`, which is what every rule downstream reads. *
|
|
23
|
+
* Importing the package root ships every subset the face offers — Latin, Latin
|
|
24
|
+
* Extended, Cyrillic, Vietnamese — but each `@font-face` carries a
|
|
25
|
+
* `unicode-range`, so a browser downloads only the subsets the page actually
|
|
26
|
+
* uses. The deployed bundle is larger than a single hand-picked file; what a
|
|
27
|
+
* reader fetches is not, and a page in Cyrillic now gets its face instead of a
|
|
28
|
+
* fallback.
|
|
29
|
+
*/
|
|
17
30
|
|
|
18
31
|
/**
|
|
19
32
|
* Blank-template root layout.
|
|
@@ -38,9 +51,7 @@ export default function RootLayout({
|
|
|
38
51
|
}>) {
|
|
39
52
|
return (
|
|
40
53
|
<html lang="en" suppressHydrationWarning>
|
|
41
|
-
<body className={
|
|
42
|
-
{children}
|
|
43
|
-
</body>
|
|
54
|
+
<body className="antialiased">{children}</body>
|
|
44
55
|
</html>
|
|
45
56
|
);
|
|
46
57
|
}
|