create-lacspace-app 1.1.0 → 1.2.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.
- package/README.md +10 -4
- package/dist/index.js +139 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -36,11 +36,17 @@ It asks for a project name and a template, scaffolds the project, installs depen
|
|
|
36
36
|
|
|
37
37
|
Every template is a real Next.js 15 App Router + Tailwind v4 project — dark, modern, responsive, with a gradient accent you can theme.
|
|
38
38
|
|
|
39
|
-
##
|
|
39
|
+
## A complete, polished project — not a blank page
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
-
|
|
41
|
+
Every generated app arrives finished, with the boring-but-essential things done:
|
|
42
|
+
|
|
43
|
+
- 🎨 **Beautiful home page** — Tailwind v4, the Inter font, dark theme + a gradient accent
|
|
44
|
+
- 🔎 **SEO** — one [`@lacspace/seo`](https://www.npmjs.com/package/@lacspace/seo) `defineSite()` config drives your `<title>`, canonical, Open Graph, Twitter card and JSON-LD
|
|
45
|
+
- ✨ **Dynamic OG images** — every page auto-generates a social-share card at `/og` (no design tool)
|
|
46
|
+
- 🛡️ **Security headers** — HSTS, CSP, X-Frame-Options and more via [`@lacspace/headers`](https://www.npmjs.com/package/@lacspace/headers)
|
|
47
|
+
- 🗺️ **robots.txt + sitemap.xml** — generated from your config with [`@lacspace/robots`](https://www.npmjs.com/package/@lacspace/robots) & [`@lacspace/sitemap`](https://www.npmjs.com/package/@lacspace/sitemap)
|
|
48
|
+
- 🧩 **A styled 404, a PWA manifest, and `.env.example`** — the finishing touches most starters skip
|
|
49
|
+
- 🎁 **A `WELCOME.md`** that walks you through everything and shows the bonus packages you can drop in
|
|
44
50
|
|
|
45
51
|
Edit `lib/site.ts` and `app/page.tsx` and you're off.
|
|
46
52
|
|
package/dist/index.js
CHANGED
|
@@ -100,38 +100,59 @@ var globalsCss = (ctx) => `@import "tailwindcss";
|
|
|
100
100
|
:root {
|
|
101
101
|
--accent-from: ${ctx.template.accent[0]};
|
|
102
102
|
--accent-to: ${ctx.template.accent[1]};
|
|
103
|
+
--bg: #0a0a0f;
|
|
104
|
+
--fg: #e5e7eb;
|
|
103
105
|
}
|
|
104
106
|
|
|
107
|
+
* { border-color: rgb(255 255 255 / 0.08); }
|
|
105
108
|
html { scroll-behavior: smooth; }
|
|
106
|
-
body {
|
|
109
|
+
body {
|
|
110
|
+
background: var(--bg);
|
|
111
|
+
color: var(--fg);
|
|
112
|
+
-webkit-font-smoothing: antialiased;
|
|
113
|
+
text-rendering: optimizeLegibility;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
::selection { background: var(--accent-to); color: #0a0a0f; }
|
|
117
|
+
::-webkit-scrollbar { width: 10px; height: 10px; }
|
|
118
|
+
::-webkit-scrollbar-thumb { background: rgb(255 255 255 / 0.12); border-radius: 8px; }
|
|
119
|
+
:focus-visible { outline: 2px solid var(--accent-to); outline-offset: 2px; border-radius: 4px; }
|
|
107
120
|
|
|
108
121
|
.gradient-text {
|
|
109
122
|
background: linear-gradient(120deg, var(--accent-from), var(--accent-to));
|
|
110
123
|
-webkit-background-clip: text; background-clip: text; color: transparent;
|
|
111
124
|
}
|
|
112
125
|
.gradient-bg { background: linear-gradient(120deg, var(--accent-from), var(--accent-to)); }
|
|
126
|
+
|
|
127
|
+
/* soft entrance for content */
|
|
128
|
+
@keyframes rise { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: none; } }
|
|
129
|
+
main > section, main > * { animation: rise 0.6s cubic-bezier(0.22, 1, 0.36, 1) both; }
|
|
130
|
+
@media (prefers-reduced-motion: reduce) { *, ::before, ::after { animation: none !important; scroll-behavior: auto; } }
|
|
113
131
|
`;
|
|
114
132
|
var siteTs = (ctx) => `import { defineSite } from "@lacspace/seo";
|
|
115
133
|
|
|
116
134
|
/** Your site's SEO configuration \u2014 set once, used everywhere. */
|
|
117
135
|
export const site = defineSite({
|
|
118
136
|
name: ${JSON.stringify(ctx.template.siteName)},
|
|
119
|
-
url: "https://example.com",
|
|
137
|
+
url: process.env.NEXT_PUBLIC_SITE_URL ?? "https://example.com",
|
|
120
138
|
description: ${JSON.stringify(ctx.template.siteDescription)},
|
|
121
139
|
// twitter: "yourhandle",
|
|
122
|
-
|
|
140
|
+
ogImage: "/og", // \u2728 auto social-share images \u2014 see app/og/route.tsx
|
|
123
141
|
});
|
|
124
142
|
`;
|
|
125
143
|
var layout = (ctx) => `import type { Metadata } from "next";
|
|
144
|
+
import { Inter } from "next/font/google";
|
|
126
145
|
import { site } from "@/lib/site";
|
|
127
146
|
import "./globals.css";
|
|
128
147
|
|
|
148
|
+
const inter = Inter({ subsets: ["latin"], display: "swap" });
|
|
149
|
+
|
|
129
150
|
export const metadata: Metadata = site.meta({ title: ${JSON.stringify(ctx.template.siteName)} });
|
|
130
151
|
|
|
131
152
|
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
132
153
|
return (
|
|
133
|
-
<html lang="en">
|
|
134
|
-
<body>
|
|
154
|
+
<html lang="en" className={inter.className}>
|
|
155
|
+
<body className="antialiased">
|
|
135
156
|
{children}
|
|
136
157
|
<script
|
|
137
158
|
type="application/ld+json"
|
|
@@ -142,6 +163,113 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|
|
142
163
|
);
|
|
143
164
|
}
|
|
144
165
|
`;
|
|
166
|
+
var ogRoute = (ctx) => `import { ImageResponse } from "next/og";
|
|
167
|
+
import { site } from "@/lib/site";
|
|
168
|
+
|
|
169
|
+
// \u2728 Dynamic Open Graph images \u2014 every page gets a gorgeous social card,
|
|
170
|
+
// generated on the fly at /og?title=Your+Page+Title. No design tool needed.
|
|
171
|
+
export const runtime = "edge";
|
|
172
|
+
|
|
173
|
+
export function GET(req: Request) {
|
|
174
|
+
const title = new URL(req.url).searchParams.get("title") ?? site.config.name;
|
|
175
|
+
return new ImageResponse(
|
|
176
|
+
(
|
|
177
|
+
<div
|
|
178
|
+
style={{
|
|
179
|
+
width: "100%", height: "100%", display: "flex", flexDirection: "column",
|
|
180
|
+
justifyContent: "center", padding: "90px",
|
|
181
|
+
background: "linear-gradient(135deg, ${ctx.template.accent[0]}, ${ctx.template.accent[1]})",
|
|
182
|
+
color: "white", fontFamily: "sans-serif",
|
|
183
|
+
}}
|
|
184
|
+
>
|
|
185
|
+
<div style={{ fontSize: 72, fontWeight: 800, lineHeight: 1.05, letterSpacing: "-0.02em" }}>{title}</div>
|
|
186
|
+
<div style={{ marginTop: 28, fontSize: 34, opacity: 0.85 }}>{site.config.name}</div>
|
|
187
|
+
</div>
|
|
188
|
+
),
|
|
189
|
+
{ width: 1200, height: 630 },
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
`;
|
|
193
|
+
var notFound = () => `import Link from "next/link";
|
|
194
|
+
|
|
195
|
+
export default function NotFound() {
|
|
196
|
+
return (
|
|
197
|
+
<main className="flex min-h-screen flex-col items-center justify-center gap-6 px-6 text-center">
|
|
198
|
+
<div className="text-8xl font-black gradient-text">404</div>
|
|
199
|
+
<p className="text-lg text-white/60">This page wandered off.</p>
|
|
200
|
+
<Link href="/" className="gradient-bg rounded-full px-6 py-3 font-semibold text-black">Back home</Link>
|
|
201
|
+
</main>
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
`;
|
|
205
|
+
var manifestTs = (ctx) => `import type { MetadataRoute } from "next";
|
|
206
|
+
import { site } from "@/lib/site";
|
|
207
|
+
|
|
208
|
+
export default function manifest(): MetadataRoute.Manifest {
|
|
209
|
+
return {
|
|
210
|
+
name: site.config.name,
|
|
211
|
+
short_name: site.config.name,
|
|
212
|
+
description: site.config.description,
|
|
213
|
+
start_url: "/",
|
|
214
|
+
display: "standalone",
|
|
215
|
+
background_color: "#0a0a0f",
|
|
216
|
+
theme_color: "${ctx.template.accent[0]}",
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
`;
|
|
220
|
+
var envExample = () => `# Your production URL \u2014 powers canonical URLs, sitemap, robots and OG images.
|
|
221
|
+
NEXT_PUBLIC_SITE_URL=https://example.com
|
|
222
|
+
`;
|
|
223
|
+
var welcomeMd = (ctx) => `# \u{1F381} Welcome to ${ctx.name}
|
|
224
|
+
|
|
225
|
+
You didn't get a blank page \u2014 you got a running, good-looking **${ctx.template.label}** with the
|
|
226
|
+
boring-but-essential stuff already done. Here's what's in the box.
|
|
227
|
+
|
|
228
|
+
## \u2705 Already set up for you
|
|
229
|
+
|
|
230
|
+
- **Beautiful home page** \u2014 styled with Tailwind v4, Inter font, dark theme + gradient accent.
|
|
231
|
+
- **SEO** \u2014 metadata, Open Graph, Twitter cards & JSON-LD, all from one file (\`lib/site.ts\`).
|
|
232
|
+
- **\u2728 Dynamic OG images** \u2014 every page auto-generates a social-share card at \`/og\`. Share a link and see.
|
|
233
|
+
- **Security headers** \u2014 HSTS, CSP, X-Frame-Options and more, via \`next.config.mjs\`.
|
|
234
|
+
- **robots.txt + sitemap.xml** \u2014 generated from your site config. No hand-editing.
|
|
235
|
+
- **A styled 404** and a **PWA manifest** \u2014 the finishing touches most starters skip.
|
|
236
|
+
|
|
237
|
+
## \u{1F680} Run it
|
|
238
|
+
|
|
239
|
+
\`\`\`bash
|
|
240
|
+
npm run dev # http://localhost:3000
|
|
241
|
+
\`\`\`
|
|
242
|
+
|
|
243
|
+
## \u{1F3A8} Make it yours (start here)
|
|
244
|
+
|
|
245
|
+
1. Edit **\`lib/site.ts\`** \u2014 your name, URL and description flow into SEO, sitemap, robots and OG.
|
|
246
|
+
2. Edit **\`app/page.tsx\`** \u2014 your home page.
|
|
247
|
+
3. Set **\`NEXT_PUBLIC_SITE_URL\`** in \`.env\` before deploying (copy \`.env.example\`).
|
|
248
|
+
|
|
249
|
+
## \u{1F389} Surprise: 49 more Lacspace packages, one install away
|
|
250
|
+
|
|
251
|
+
Your app is wired for the whole ecosystem. Drop any of these in \u2014 all zero-dependency:
|
|
252
|
+
|
|
253
|
+
\`\`\`bash
|
|
254
|
+
npm i @lacspace/id # uuidv7, nanoid, short ids
|
|
255
|
+
npm i @lacspace/pdf # invoices & receipts, no headless browser
|
|
256
|
+
npm i @lacspace/signed-url # magic-login & expiring download links
|
|
257
|
+
npm i @lacspace/webhooks # sign / verify / deliver webhooks
|
|
258
|
+
npm i @lacspace/flags # feature flags & A/B, no SaaS
|
|
259
|
+
npm i @lacspace/humanize # "1.5 KB", "3 hours ago", "1.2M"
|
|
260
|
+
\`\`\`
|
|
261
|
+
|
|
262
|
+
Browse them all \u2192 **https://lacspace.com/packages**
|
|
263
|
+
|
|
264
|
+
## \u2601\uFE0F Deploy
|
|
265
|
+
|
|
266
|
+
Push to GitHub and import on **[Vercel](https://vercel.com/new)** \u2014 it just works. Remember to set
|
|
267
|
+
\`NEXT_PUBLIC_SITE_URL\` to your real domain.
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
Built with \u2764\uFE0F using [Lacspace](https://lacspace.com/packages). This app is yours under the Lacspace Free Licence.
|
|
272
|
+
`;
|
|
145
273
|
var robotsTs = () => `import { robotsForSite } from "@lacspace/robots";
|
|
146
274
|
import { site } from "@/lib/site";
|
|
147
275
|
|
|
@@ -450,8 +578,13 @@ function buildFiles(ctx) {
|
|
|
450
578
|
"app/layout.tsx": layout(ctx),
|
|
451
579
|
"app/globals.css": globalsCss(ctx),
|
|
452
580
|
"app/page.tsx": homePage(ctx),
|
|
581
|
+
"app/not-found.tsx": notFound(),
|
|
582
|
+
"app/og/route.tsx": ogRoute(ctx),
|
|
583
|
+
"app/manifest.ts": manifestTs(ctx),
|
|
453
584
|
"app/robots.txt/route.ts": robotsTs(),
|
|
454
|
-
"app/sitemap.xml/route.ts": sitemapTs()
|
|
585
|
+
"app/sitemap.xml/route.ts": sitemapTs(),
|
|
586
|
+
".env.example": envExample(),
|
|
587
|
+
"WELCOME.md": welcomeMd(ctx)
|
|
455
588
|
};
|
|
456
589
|
}
|
|
457
590
|
function parseArgs(list) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-lacspace-app",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Scaffold a beautiful, production-ready Next.js app from a Lacspace template — portfolio, business, e-commerce, SaaS, blog, docs, dashboard or restaurant — pre-wired with SEO, security headers, sitemap and robots. Like create-next-app, but you start gorgeous.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|