create-nextly-app 0.0.1 → 0.0.2-alpha.0

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.
Files changed (53) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +131 -0
  3. package/bin/create-nextly-app.js +3 -0
  4. package/dist/chunk-AYJ2RKVJ.mjs +28752 -0
  5. package/dist/chunk-AYJ2RKVJ.mjs.map +1 -0
  6. package/dist/cli.cjs +32410 -0
  7. package/dist/cli.cjs.map +1 -0
  8. package/dist/cli.d.cts +1 -0
  9. package/dist/cli.d.ts +1 -0
  10. package/dist/cli.mjs +3633 -0
  11. package/dist/cli.mjs.map +1 -0
  12. package/dist/index.cjs +24776 -0
  13. package/dist/index.cjs.map +1 -0
  14. package/dist/index.d.cts +69 -0
  15. package/dist/index.d.ts +69 -0
  16. package/dist/index.mjs +6 -0
  17. package/dist/index.mjs.map +1 -0
  18. package/package.json +83 -10
  19. package/templates/base/.env.example +16 -0
  20. package/templates/base/README.md +30 -0
  21. package/templates/base/eslint.config.mjs +18 -0
  22. package/templates/base/next.config.ts +21 -0
  23. package/templates/base/postcss.config.mjs +7 -0
  24. package/templates/base/public/file.svg +1 -0
  25. package/templates/base/public/globe.svg +1 -0
  26. package/templates/base/public/next.svg +1 -0
  27. package/templates/base/public/vercel.svg +1 -0
  28. package/templates/base/public/window.svg +1 -0
  29. package/templates/base/src/app/admin/[[...params]]/layout.tsx +20 -0
  30. package/templates/base/src/app/admin/[[...params]]/page.tsx +27 -0
  31. package/templates/base/src/app/admin/api/[[...params]]/route.ts +12 -0
  32. package/templates/base/src/app/api/health/route.ts +7 -0
  33. package/templates/base/src/app/api/media/[[...path]]/route.ts +34 -0
  34. package/templates/base/src/app/favicon.ico +0 -0
  35. package/templates/base/src/app/globals.css +55 -0
  36. package/templates/base/src/app/layout.tsx +43 -0
  37. package/templates/base/src/app/page.tsx +65 -0
  38. package/templates/base/src/types/generated/.gitkeep +0 -0
  39. package/templates/base/src/types/generated/nextly-types.ts +12 -0
  40. package/templates/base/tsconfig.json +35 -0
  41. package/templates/blank/.env.example +8 -0
  42. package/templates/blank/README.md +63 -0
  43. package/templates/blank/nextly.config.ts +14 -0
  44. package/templates/blank/src/access/README.md +22 -0
  45. package/templates/blank/src/app/(frontend)/page.tsx +176 -0
  46. package/templates/blank/src/app/globals.css +79 -0
  47. package/templates/blank/src/app/layout.tsx +46 -0
  48. package/templates/blank/src/collections/README.md +22 -0
  49. package/templates/blank/src/components/README.md +10 -0
  50. package/templates/blank/src/components/ThemeToggle.tsx +113 -0
  51. package/templates/blank/src/lib/README.md +15 -0
  52. package/templates/blank/src/singles/README.md +19 -0
  53. package/templates/blank/template.json +14 -0
@@ -0,0 +1,35 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2017",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "react-jsx",
15
+ "incremental": true,
16
+ "plugins": [
17
+ {
18
+ "name": "next"
19
+ }
20
+ ],
21
+ "paths": {
22
+ "@/*": ["./src/*"],
23
+ "@nextly-config": ["./nextly.config.ts"]
24
+ }
25
+ },
26
+ "include": [
27
+ "next-env.d.ts",
28
+ "**/*.ts",
29
+ "**/*.tsx",
30
+ ".next/types/**/*.ts",
31
+ ".next/dev/types/**/*.ts",
32
+ "**/*.mts"
33
+ ],
34
+ "exclude": ["node_modules"]
35
+ }
@@ -0,0 +1,8 @@
1
+ # Public site URL used by:
2
+ # - Metadata API (canonical URLs, OpenGraph image resolution)
3
+ # - sitemap.xml, robots.txt, and any RSS feed route handlers you add
4
+ # - JSON-LD structured data links
5
+ #
6
+ # Set this to your production domain when deploying (e.g. https://yourapp.com).
7
+ # The localhost fallback keeps local development working.
8
+ NEXT_PUBLIC_SITE_URL=http://localhost:3000
@@ -0,0 +1,63 @@
1
+ # Blank template
2
+
3
+ A minimal Nextly project with the conventional folder layout already
4
+ scaffolded. Empty `nextly.config.ts`, no collections, one landing
5
+ page, and READMEs in every convention folder so you know where things
6
+ go as you grow.
7
+
8
+ ## When to use
9
+
10
+ Choose `blank` when you want to build everything from scratch but
11
+ appreciate having the right folder structure waiting for you. Pick
12
+ [`blog`](../blog/README.md) instead if you want a populated example
13
+ with collections, singles, frontend pages, RSS, search, and demo
14
+ content already wired up.
15
+
16
+ ## Scaffold
17
+
18
+ ```bash
19
+ pnpm create nextly-app my-app --template blank
20
+ ```
21
+
22
+ ## What you get
23
+
24
+ ```
25
+ my-app/
26
+ ├── nextly.config.ts # Empty config — add your collections / singles
27
+ ├── .env.example # NEXT_PUBLIC_SITE_URL etc.
28
+ ├── next.config.ts # (inherited from base)
29
+ ├── postcss.config.mjs # (inherited from base)
30
+ ├── tsconfig.json # (inherited from base)
31
+ └── src/
32
+ ├── access/ # RBAC functions (anyone, authenticated, ...)
33
+ ├── collections/ # defineCollection() definitions
34
+ ├── singles/ # defineSingle() definitions
35
+ ├── components/ # React components (ThemeToggle ships as an example)
36
+ ├── lib/ # Project-wide helpers
37
+ └── app/
38
+ ├── layout.tsx # Root layout (font + metadata)
39
+ ├── globals.css # Design tokens
40
+ └── (frontend)/ # Public-facing routes (route group)
41
+ └── page.tsx # Landing page — flips between "Set up admin" / "Open admin"
42
+ ```
43
+
44
+ The `(frontend)` route group keeps your public pages cleanly separated
45
+ from `app/admin/...` and `app/api/...` routes that Nextly mounts. As
46
+ you add routes, drop them inside `(frontend)/` and they'll inherit
47
+ your blog/marketing layout instead of the admin's.
48
+
49
+ Every convention folder ships with a `README.md` explaining what
50
+ belongs there. Replace them with real code as you build.
51
+
52
+ ## Next steps
53
+
54
+ - **Add a collection** — create `src/collections/Posts.ts`, register
55
+ it in `nextly.config.ts`, run `pnpm dev`. The admin will surface it
56
+ immediately.
57
+ - **Add a public page** — drop a new file under
58
+ `src/app/(frontend)/blog/page.tsx`. It'll be live at `/blog`.
59
+ - **Configure storage / email** — see
60
+ [`nextlyhq.com/docs`](https://nextlyhq.com/docs) for adapter setup.
61
+
62
+ See [`templates/blog/README.md`](../blog/README.md) for a fully
63
+ populated example showing all of the above wired up.
@@ -0,0 +1,14 @@
1
+ import { defineConfig } from "nextly/config";
2
+
3
+ export default defineConfig({
4
+ // Add your collections here
5
+ collections: [],
6
+
7
+ // Add your singles (globals) here
8
+ singles: [],
9
+
10
+ // TypeScript type generation
11
+ typescript: {
12
+ outputFile: "./src/types/generated/nextly-types.ts",
13
+ },
14
+ });
@@ -0,0 +1,22 @@
1
+ # Access
2
+
3
+ Access-control functions used by `defineCollection({ access: ... })`
4
+ and `defineSingle({ access: ... })`. Each function decides whether the
5
+ current user can perform an action (read, create, update, delete) on a
6
+ collection or single.
7
+
8
+ Conventions:
9
+
10
+ - One function per file. File name matches the function name.
11
+ - Export the function as a named export.
12
+ - Type signature: `AccessControlFunction` from `nextly`.
13
+
14
+ Common patterns shipped in the blog template (for reference):
15
+
16
+ - `anyone.ts` — always allow (use for public reads).
17
+ - `authenticated.ts` — require any logged-in user.
18
+ - `is-admin.ts` — require the `admin` role.
19
+ - `is-author-or-editor.ts` — require one of several roles.
20
+
21
+ Add your own here as your project grows. Wire them up in your
22
+ collections / singles config.
@@ -0,0 +1,176 @@
1
+ import Link from "next/link";
2
+
3
+ import { ThemeToggle } from "@/components/ThemeToggle";
4
+
5
+ /**
6
+ * Blank-template landing page (public route via the (frontend) route group).
7
+ *
8
+ * Replaces the inherited Next.js placeholder at `/` with a minimal,
9
+ * brutalist-edge welcome page that points the developer at the admin
10
+ * panel and the docs. Server Component — fetches setup-status so the
11
+ * primary button label flips between "Set up admin" (no super-admin
12
+ * yet) and "Open admin" (super-admin exists).
13
+ *
14
+ * If the auth API fails for any reason (DB not migrated, network),
15
+ * the button defaults to "Open admin" / /admin since /admin already
16
+ * redirects to /admin/setup when no super-admin exists — the link
17
+ * works either way.
18
+ *
19
+ * Edit this file in src/app/(frontend)/page.tsx after scaffolding to customise.
20
+ */
21
+
22
+ interface SetupStatus {
23
+ isSetup: boolean;
24
+ requiresInitialUser: boolean;
25
+ }
26
+
27
+ async function fetchSetupStatus(): Promise<SetupStatus | null> {
28
+ try {
29
+ const baseUrl = process.env.NEXT_PUBLIC_SITE_URL ?? "http://localhost:3000";
30
+ const res = await fetch(`${baseUrl}/api/auth/setup-status`, {
31
+ cache: "no-store",
32
+ });
33
+ if (!res.ok) return null;
34
+ return (await res.json()) as SetupStatus;
35
+ } catch {
36
+ return null;
37
+ }
38
+ }
39
+
40
+ export default async function HomePage() {
41
+ const status = await fetchSetupStatus();
42
+ const needsSetup = status?.requiresInitialUser ?? false;
43
+ const adminLabel = needsSetup ? "Set up admin" : "Open admin";
44
+ const adminHref = needsSetup ? "/admin/setup" : "/admin";
45
+
46
+ return (
47
+ <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
+ <ThemeToggle />
49
+ <div className="max-w-[640px] w-full text-center">
50
+ <div className="flex items-center justify-center mb-8">
51
+ <svg
52
+ width="180"
53
+ height="48"
54
+ viewBox="0 0 235 63"
55
+ fill="none"
56
+ xmlns="http://www.w3.org/2000/svg"
57
+ className="text-foreground"
58
+ >
59
+ <rect width="62" height="63" fill="currentColor" />
60
+ <path
61
+ d="M18 46.7034L24.6253 43.6455V26.6564L32.9494 30.5448V23.26L18 16.21V46.7034Z"
62
+ fill="var(--color-background)"
63
+ />
64
+ <path
65
+ d="M44 16.2102L37.3747 19.268V36.2905L29.035 32.4021L29.0506 39.6536L44 46.7036V16.2102Z"
66
+ fill="var(--color-background)"
67
+ />
68
+ <path
69
+ d="M116.233 11.0908V45.9999H109.858L94.6705 24.0283H94.4148V45.9999H87.0341V11.0908H93.5114L108.58 33.0454H108.886V11.0908H116.233ZM134.126 46.5113C131.433 46.5113 129.115 45.9658 127.172 44.8749C125.24 43.7726 123.751 42.2158 122.706 40.2044C121.661 38.1817 121.138 35.7897 121.138 33.0283C121.138 30.3351 121.661 27.9715 122.706 25.9374C123.751 23.9033 125.223 22.3181 127.121 21.1817C129.03 20.0454 131.268 19.4772 133.837 19.4772C135.564 19.4772 137.172 19.7556 138.661 20.3124C140.161 20.8579 141.467 21.6817 142.581 22.784C143.706 23.8863 144.581 25.2726 145.206 26.9431C145.831 28.6022 146.143 30.5454 146.143 32.7726V34.7669H124.036V30.2669H139.308C139.308 29.2215 139.081 28.2954 138.626 27.4885C138.172 26.6817 137.541 26.051 136.734 25.5965C135.939 25.1306 135.013 24.8976 133.956 24.8976C132.854 24.8976 131.876 25.1533 131.024 25.6647C130.183 26.1647 129.524 26.8408 129.047 27.6931C128.57 28.534 128.325 29.4715 128.314 30.5056V34.784C128.314 36.0794 128.553 37.1988 129.03 38.1419C129.518 39.0851 130.206 39.8124 131.092 40.3238C131.979 40.8351 133.03 41.0908 134.246 41.0908C135.053 41.0908 135.791 40.9772 136.462 40.7499C137.132 40.5226 137.706 40.1817 138.183 39.7272C138.661 39.2726 139.024 38.7158 139.274 38.0567L145.99 38.4999C145.649 40.1135 144.95 41.5226 143.893 42.7272C142.848 43.9204 141.496 44.8522 139.837 45.5226C138.189 46.1817 136.286 46.5113 134.126 46.5113ZM156.482 19.8181L161.288 28.9715L166.214 19.8181H173.663L166.078 32.909L173.868 45.9999H166.453L161.288 36.9488L156.209 45.9999H148.709L156.482 32.909L148.982 19.8181H156.482ZM191.902 19.8181V25.2726H176.135V19.8181H191.902ZM179.714 13.5454H186.976V37.9544C186.976 38.6249 187.078 39.1476 187.283 39.5226C187.487 39.8863 187.771 40.1419 188.135 40.2897C188.51 40.4374 188.942 40.5113 189.43 40.5113C189.771 40.5113 190.112 40.4829 190.453 40.426C190.794 40.3579 191.055 40.3067 191.237 40.2726L192.379 45.676C192.016 45.7897 191.504 45.9204 190.845 46.0681C190.186 46.2272 189.385 46.3238 188.442 46.3579C186.692 46.426 185.158 46.1931 183.839 45.659C182.533 45.1249 181.516 44.2954 180.788 43.1704C180.061 42.0454 179.703 40.6249 179.714 38.909V13.5454ZM204.456 11.0908V45.9999H197.195V11.0908H204.456ZM214.756 55.8181C213.835 55.8181 212.972 55.7442 212.165 55.5965C211.369 55.4601 210.71 55.284 210.188 55.0681L211.824 49.6476C212.676 49.909 213.443 50.051 214.125 50.0738C214.818 50.0965 215.415 49.9374 215.915 49.5965C216.426 49.2556 216.841 48.676 217.159 47.8579L217.585 46.7499L208.193 19.8181H215.83L221.25 39.0454H221.523L226.994 19.8181H234.682L224.506 48.8294C224.017 50.2385 223.352 51.4658 222.511 52.5113C221.682 53.5681 220.631 54.3806 219.358 54.9488C218.085 55.5283 216.551 55.8181 214.756 55.8181Z"
70
+ fill="currentColor"
71
+ />
72
+ </svg>
73
+ <span className="ml-4 font-mono text-[10px] font-semibold tracking-wide uppercase px-2 py-0.5 bg-foreground/5 text-slate-500">
74
+ Alpha
75
+ </span>
76
+ </div>
77
+
78
+ <h1 className="mx-auto font-semibold tracking-[-0.03em] leading-[1.1] text-[clamp(24px,5vw,32px)]">
79
+ Welcome to your new Nextly project.
80
+ </h1>
81
+
82
+ <p className="mt-6 mx-auto text-slate-500 dark:text-slate-400 max-w-[42ch] text-[clamp(16px,1.8vw,18px)] leading-[1.6]">
83
+ This is the blank template. Open the admin to create your first
84
+ collection or read the docs to see what Nextly can do.
85
+ </p>
86
+
87
+ <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>
106
+ <Link href="https://nextlyhq.com/docs" className="btn-secondary">
107
+ <span>Read documentation</span>
108
+ <svg
109
+ xmlns="http://www.w3.org/2000/svg"
110
+ width="18"
111
+ height="18"
112
+ viewBox="0 0 24 24"
113
+ fill="none"
114
+ stroke="currentColor"
115
+ strokeWidth="2"
116
+ strokeLinecap="round"
117
+ strokeLinejoin="round"
118
+ className="lucide lucide-arrow-up-right"
119
+ >
120
+ <path d="M7 7h10v10" />
121
+ <path d="M7 17 17 7" />
122
+ </svg>
123
+ </Link>
124
+ </div>
125
+
126
+ <div className="mt-16 pt-8 border-t border-foreground/5 flex flex-col sm:flex-row items-center justify-between gap-6">
127
+ <p className="font-mono text-[11px] tracking-tight text-slate-500">
128
+ Edit in{" "}
129
+ <span className="text-foreground">src/app/(frontend)/page.tsx</span>
130
+ </p>
131
+ <div className="flex gap-6">
132
+ <Link
133
+ href="https://github.com/nextlyhq/nextly"
134
+ className="flex items-center gap-2 text-[12px] font-medium text-slate-500 hover:text-foreground transition-colors"
135
+ >
136
+ <svg
137
+ aria-hidden="true"
138
+ focusable="false"
139
+ viewBox="0 0 24 24"
140
+ width="16"
141
+ height="16"
142
+ fill="currentColor"
143
+ style={{ verticalAlign: "text-bottom" }}
144
+ >
145
+ <path d="M10.226 17.284c-2.965-.36-5.054-2.493-5.054-5.256 0-1.123.404-2.336 1.078-3.144-.292-.741-.247-2.314.09-2.965.898-.112 2.111.36 2.83 1.01.853-.269 1.752-.404 2.853-.404 1.1 0 1.999.135 2.807.382.696-.629 1.932-1.1 2.83-.988.315.606.36 2.179.067 2.942.72.854 1.101 2 1.101 3.167 0 2.763-2.089 4.852-5.098 5.234.763.494 1.28 1.572 1.28 2.807v2.336c0 .674.561 1.056 1.235.786 4.066-1.55 7.255-5.615 7.255-10.646C23.5 6.188 18.334 1 11.978 1 5.62 1 .5 6.188.5 12.545c0 4.986 3.167 9.12 7.435 10.669.606.225 1.19-.18 1.19-.786V20.63a2.9 2.9 0 0 1-1.078.224c-1.483 0-2.359-.808-2.987-2.313-.247-.607-.517-.966-1.034-1.033-.27-.023-.359-.135-.359-.27 0-.27.45-.471.898-.471.652 0 1.213.404 1.797 1.235.45.651.921.943 1.483.943.561 0 .92-.202 1.437-.719.382-.381.674-.718.944-.943"></path>
146
+ </svg>
147
+ <span>GitHub</span>
148
+ </Link>
149
+ <Link
150
+ href="https://nextlyhq.com"
151
+ className="flex items-center gap-2 text-[12px] font-medium text-slate-500 hover:text-foreground transition-colors"
152
+ >
153
+ <svg
154
+ xmlns="http://www.w3.org/2000/svg"
155
+ width="16"
156
+ height="16"
157
+ viewBox="0 0 24 24"
158
+ fill="none"
159
+ stroke="currentColor"
160
+ strokeWidth="2"
161
+ strokeLinecap="round"
162
+ strokeLinejoin="round"
163
+ className="lucide lucide-globe"
164
+ >
165
+ <circle cx="12" cy="12" r="10" />
166
+ <path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20" />
167
+ <path d="M2 12h20" />
168
+ </svg>
169
+ <span>Website</span>
170
+ </Link>
171
+ </div>
172
+ </div>
173
+ </div>
174
+ </main>
175
+ );
176
+ }
@@ -0,0 +1,79 @@
1
+ @import "tailwindcss";
2
+
3
+ /**
4
+ * Blank-template global styles.
5
+ *
6
+ * Overrides templates/base/src/app/globals.css so the branded landing
7
+ * page renders with its supporting decorations: a subtle 56px grid
8
+ * backdrop, the corner brackets, the pulsing "Localhost / Development"
9
+ * dot, and a hover-invert helper for the secondary button.
10
+ *
11
+ * Tailwind base tokens still apply — these rules are additive.
12
+ */
13
+
14
+ :root {
15
+ --color-background: #f9fafb;
16
+ --color-foreground: #0f172a;
17
+ }
18
+
19
+ @media (prefers-color-scheme: dark) {
20
+ :root {
21
+ --color-background: #0b0f19;
22
+ --color-foreground: #f9fafb;
23
+ }
24
+ }
25
+
26
+ .dark {
27
+ --color-background: #0b0f19;
28
+ --color-foreground: #f9fafb;
29
+ }
30
+
31
+ @theme {
32
+ --color-background: var(--color-background);
33
+ --color-foreground: var(--color-foreground);
34
+ }
35
+
36
+ body {
37
+ background-color: var(--color-background);
38
+ color: var(--color-foreground);
39
+ overflow-x: hidden;
40
+ }
41
+
42
+ .pulse-dot {
43
+ display: inline-block;
44
+ width: 6px;
45
+ height: 6px;
46
+ margin-right: 6px;
47
+ background: #10b981;
48
+ border-radius: 50%;
49
+ box-shadow: 0 0 8px #10b981;
50
+ animation: nextly-pulse 1.6s ease-in-out infinite;
51
+ }
52
+
53
+ @keyframes nextly-pulse {
54
+ 0%,
55
+ 100% {
56
+ opacity: 1;
57
+ }
58
+ 50% {
59
+ opacity: 0.4;
60
+ }
61
+ }
62
+
63
+ @media (prefers-reduced-motion: reduce) {
64
+ .pulse-dot {
65
+ animation: none;
66
+ }
67
+ }
68
+
69
+ .font-display {
70
+ font-family: var(--font-display), ui-sans-serif, system-ui, sans-serif;
71
+ }
72
+
73
+ .btn-primary {
74
+ @apply inline-flex items-center gap-2.5 rounded-none bg-foreground px-6 py-3.5 text-sm font-semibold text-background transition-all hover:opacity-90 active:scale-95;
75
+ }
76
+
77
+ .btn-secondary {
78
+ @apply inline-flex items-center gap-2.5 rounded-none border border-foreground/10 bg-transparent px-6 py-3.5 text-sm font-medium text-foreground transition-all hover:bg-foreground/5 active:scale-95;
79
+ }
@@ -0,0 +1,46 @@
1
+ import type { Metadata } from "next";
2
+ import { Inter, JetBrains_Mono } from "next/font/google";
3
+
4
+ import "./globals.css";
5
+
6
+ const display = Inter({
7
+ variable: "--font-display",
8
+ subsets: ["latin"],
9
+ display: "swap",
10
+ });
11
+
12
+ const mono = JetBrains_Mono({
13
+ variable: "--font-mono",
14
+ subsets: ["latin"],
15
+ display: "swap",
16
+ });
17
+
18
+ /**
19
+ * Blank-template root layout.
20
+ *
21
+ * Overrides templates/base/src/app/layout.tsx so the landing page can
22
+ * use a distinctive font pairing (Bricolage Grotesque for display,
23
+ * JetBrains Mono for tech accents). `metadataBase` is set so future
24
+ * blank-template pages can use relative OG image URLs without breaking.
25
+ */
26
+ export const metadata: Metadata = {
27
+ metadataBase: new URL(
28
+ process.env.NEXT_PUBLIC_SITE_URL ?? "http://localhost:3000"
29
+ ),
30
+ title: { default: "Nextly", template: "%s — Nextly" },
31
+ description: "A Nextly project.",
32
+ };
33
+
34
+ export default function RootLayout({
35
+ children,
36
+ }: Readonly<{
37
+ children: React.ReactNode;
38
+ }>) {
39
+ return (
40
+ <html lang="en" suppressHydrationWarning>
41
+ <body className={`${display.variable} ${mono.variable} antialiased`}>
42
+ {children}
43
+ </body>
44
+ </html>
45
+ );
46
+ }
@@ -0,0 +1,22 @@
1
+ # Collections
2
+
3
+ Code-first collections. Each collection is defined via
4
+ `defineCollection({ ... })` from `nextly/config` and wired into the
5
+ root `nextly.config.ts` as part of the `collections: [...]` array.
6
+
7
+ Conventions:
8
+
9
+ - Trivial collections (a few scalar fields, no hooks): single file
10
+ named after the collection (e.g. `Categories.ts`).
11
+ - Complex collections (many fields, hooks, or per-collection helpers):
12
+ folder named after the collection, with `index.ts` exporting the
13
+ collection definition and a `hooks/` subdir for hooks specific to
14
+ that collection (e.g. `Posts/index.ts` + `Posts/hooks/`).
15
+ - Imports inside collection files use relative paths (`../access/...`)
16
+ rather than the `@/` alias because `nextly.config.ts` is loaded by
17
+ the CLI through plain Node.js module resolution, which does not
18
+ honour TypeScript path aliases.
19
+
20
+ Skip this folder if you're using the Visual Schema Builder (admin UI)
21
+ to manage your schemas — UI-built collections live in the database, not
22
+ on disk.
@@ -0,0 +1,10 @@
1
+ # Components
2
+
3
+ React components used by your `src/app/` routes. Server Components by
4
+ default; add `"use client"` at the top of any file that needs
5
+ interactivity.
6
+
7
+ The blank template ships one example: `ThemeToggle.tsx` (used on the
8
+ landing page). Add your own components here as your project grows.
9
+
10
+ Use the `@/` path alias to import: `import { Foo } from "@/components/Foo"`.
@@ -0,0 +1,113 @@
1
+ "use client";
2
+
3
+ import { useEffect, useState } from "react";
4
+
5
+ type Theme = "light" | "dark" | "system";
6
+
7
+ export function ThemeToggle() {
8
+ const [theme, setTheme] = useState<Theme>("system");
9
+
10
+ useEffect(() => {
11
+ const savedTheme = localStorage.getItem("theme") as Theme | null;
12
+ if (savedTheme) {
13
+ setTheme(savedTheme);
14
+ applyTheme(savedTheme);
15
+ } else {
16
+ applyTheme("system");
17
+ }
18
+ }, []);
19
+
20
+ const applyTheme = (newTheme: Theme) => {
21
+ const isDark =
22
+ newTheme === "dark" ||
23
+ (newTheme === "system" &&
24
+ window.matchMedia("(prefers-color-scheme: dark)").matches);
25
+
26
+ document.documentElement.classList.toggle("dark", isDark);
27
+ };
28
+
29
+ const handleThemeChange = (newTheme: Theme) => {
30
+ setTheme(newTheme);
31
+ localStorage.setItem("theme", newTheme);
32
+ applyTheme(newTheme);
33
+ };
34
+
35
+ return (
36
+ <div className="fixed top-6 right-6 flex items-center border border-foreground/10 bg-background p-1 z-50">
37
+ <button
38
+ onClick={() => handleThemeChange("light")}
39
+ className={`p-1.5 transition-colors cursor-pointer ${
40
+ theme === "light"
41
+ ? "text-foreground bg-foreground/5"
42
+ : "text-slate-400 hover:text-foreground"
43
+ }`}
44
+ aria-label="Light theme"
45
+ >
46
+ <svg
47
+ xmlns="http://www.w3.org/2000/svg"
48
+ fill="none"
49
+ viewBox="0 0 24 24"
50
+ strokeWidth={2}
51
+ stroke="currentColor"
52
+ className="w-4 h-4"
53
+ >
54
+ <path
55
+ strokeLinecap="round"
56
+ strokeLinejoin="round"
57
+ d="M12 3v2.25m6.364.386l-1.591 1.591M21 12h-2.25m-.386 6.364l-1.591-1.591M12 18.75V21m-4.773-4.227l-1.591 1.591M3 12h2.25m.386-6.364l1.591 1.591M15.75 12a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z"
58
+ />
59
+ </svg>
60
+ </button>
61
+
62
+ <button
63
+ onClick={() => handleThemeChange("system")}
64
+ className={`p-1.5 transition-colors cursor-pointer ${
65
+ theme === "system"
66
+ ? "text-foreground bg-foreground/5"
67
+ : "text-slate-400 hover:text-foreground"
68
+ }`}
69
+ aria-label="System theme"
70
+ >
71
+ <svg
72
+ xmlns="http://www.w3.org/2000/svg"
73
+ fill="none"
74
+ viewBox="0 0 24 24"
75
+ strokeWidth={2}
76
+ stroke="currentColor"
77
+ className="w-4 h-4"
78
+ >
79
+ <path
80
+ strokeLinecap="round"
81
+ strokeLinejoin="round"
82
+ d="M9 17.25v1.007a3 3 0 01-.879 2.122L7.5 21h9l-.621-.621A3 3 0 0115 18.257V17.25m6-12V15a2.25 2.25 0 01-2.25 2.25H5.25A2.25 2.25 0 013 15V5.25m18 0A2.25 2.25 0 0018.75 3H5.25A2.25 2.25 0 003 5.25m18 0V12a2.25 2.25 0 01-2.25 2.25H5.25A2.25 2.25 0 013 12V5.25"
83
+ />
84
+ </svg>
85
+ </button>
86
+
87
+ <button
88
+ onClick={() => handleThemeChange("dark")}
89
+ className={`p-1.5 transition-colors cursor-pointer ${
90
+ theme === "dark"
91
+ ? "text-foreground bg-foreground/5"
92
+ : "text-slate-400 hover:text-foreground"
93
+ }`}
94
+ aria-label="Dark theme"
95
+ >
96
+ <svg
97
+ xmlns="http://www.w3.org/2000/svg"
98
+ fill="none"
99
+ viewBox="0 0 24 24"
100
+ strokeWidth={2}
101
+ stroke="currentColor"
102
+ className="w-4 h-4"
103
+ >
104
+ <path
105
+ strokeLinecap="round"
106
+ strokeLinejoin="round"
107
+ d="M21.752 15.002A9.718 9.718 0 0118 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 003 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 009.002-5.998z"
108
+ />
109
+ </svg>
110
+ </button>
111
+ </div>
112
+ );
113
+ }
@@ -0,0 +1,15 @@
1
+ # Lib
2
+
3
+ Project-wide helpers and utilities. Add files here for things that get
4
+ imported across multiple components or routes.
5
+
6
+ Examples (from the blog template, for inspiration):
7
+
8
+ - `format-date.ts` — locale-aware date formatting.
9
+ - `site-url.ts` — resolves canonical URLs for OG / sitemap.
10
+ - `queries/` — cached Direct-API wrappers (e.g. `getPostBySlug`).
11
+ - `rss.ts` — RSS feed builder.
12
+
13
+ Use the `@/` path alias (`import { ... } from "@/lib/foo"`) inside
14
+ your `src/app/`, `src/components/`, etc. Do not use the alias inside
15
+ `nextly.config.ts` or files transitively loaded by it.
@@ -0,0 +1,19 @@
1
+ # Singles
2
+
3
+ Code-first singles (formerly "globals"). Each single is defined via
4
+ `defineSingle({ ... })` from `nextly/config` and wired into the root
5
+ `nextly.config.ts` as part of the `singles: [...]` array.
6
+
7
+ A single is a one-of content record — Site Settings, Homepage layout,
8
+ Footer config, etc. Use Singles when there is exactly one record of a
9
+ given type, and use Collections when the type has many records.
10
+
11
+ Conventions:
12
+
13
+ - One file or folder per single. Folder if the single has hooks or
14
+ helpers; flat file if it's a small scalar definition.
15
+ - Imports inside single files use relative paths (`../access/...`)
16
+ rather than the `@/` alias because `nextly.config.ts` is loaded by
17
+ the CLI through plain Node.js module resolution.
18
+
19
+ Skip this folder if you're using the Visual Schema Builder.
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "blank",
3
+ "label": "Blank",
4
+ "description": "Start fresh with an empty config",
5
+ "hint": "Empty project, define your own schemas",
6
+ "approaches": [],
7
+ "defaultApproach": null,
8
+ "collections": [],
9
+ "singles": [],
10
+ "hasDemoData": false,
11
+ "hasFrontendPages": true,
12
+ "recommendedDatabase": "any",
13
+ "release": "alpha"
14
+ }