create-audora-next 0.1.6 → 2.0.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 +15 -5
- package/index.ts +12 -4
- package/package.json +6 -1
- package/templates/blog/README.md +164 -0
- package/templates/blog/bun.lock +1341 -0
- package/templates/blog/env.example.template +5 -0
- package/templates/blog/eslint.config.mjs +18 -0
- package/templates/blog/gitignore.template +41 -0
- package/templates/blog/husky.template/pre-commit +16 -0
- package/templates/blog/lint-staged.config.mjs +17 -0
- package/templates/blog/next.config.ts +38 -0
- package/templates/blog/package.json +59 -0
- package/templates/blog/postcss.config.mjs +7 -0
- package/templates/blog/public/favicon/apple-touch-icon.png +0 -0
- package/templates/blog/public/favicon/favicon-96x96.png +0 -0
- package/templates/blog/public/favicon/favicon.ico +0 -0
- package/templates/blog/public/favicon/favicon.svg +1 -0
- package/templates/blog/public/favicon/site.webmanifest +21 -0
- package/templates/blog/public/favicon/web-app-manifest-192x192.png +0 -0
- package/templates/blog/public/favicon/web-app-manifest-512x512.png +0 -0
- package/templates/blog/public/images/screenshot-desktop-dark.webp +0 -0
- package/templates/blog/public/images/screenshot-desktop-light.webp +0 -0
- package/templates/blog/public/images/screenshot-mobile-dark.webp +0 -0
- package/templates/blog/public/images/screenshot-mobile-light.webp +0 -0
- package/templates/blog/src/app/blogs/[slug]/page.tsx +171 -0
- package/templates/blog/src/app/blogs/page.tsx +108 -0
- package/templates/blog/src/app/layout.tsx +60 -0
- package/templates/blog/src/app/llms-full.txt/route.ts +97 -0
- package/templates/blog/src/app/llms.txt/route.ts +40 -0
- package/templates/blog/src/app/manifest.ts +61 -0
- package/templates/blog/src/app/page.tsx +57 -0
- package/templates/blog/src/app/robots.ts +16 -0
- package/templates/blog/src/app/sitemap.ts +52 -0
- package/templates/blog/src/blogs/components/animated-blog-list.tsx +33 -0
- package/templates/blog/src/blogs/components/blog-post-card.tsx +46 -0
- package/templates/blog/src/blogs/components/blog-section.tsx +34 -0
- package/templates/blog/src/blogs/components/blog-table-of-contents.tsx +369 -0
- package/templates/blog/src/blogs/components/copy-button.tsx +46 -0
- package/templates/blog/src/blogs/components/mdx.tsx +225 -0
- package/templates/blog/src/blogs/content/cosketch/cosketch-canvas-engine.mdx +186 -0
- package/templates/blog/src/blogs/content/cosketch/cosketch-docker-architecture.mdx +175 -0
- package/templates/blog/src/blogs/content/cosketch/cosketch-eraser-and-selection.mdx +207 -0
- package/templates/blog/src/blogs/content/hello-world.mdx +66 -0
- package/templates/blog/src/blogs/data/mdx.ts +68 -0
- package/templates/blog/src/blogs/utils/extract-headings.ts +38 -0
- package/templates/blog/src/components/copyable-code.tsx +41 -0
- package/templates/blog/src/components/footer.tsx +25 -0
- package/templates/blog/src/components/header.tsx +27 -0
- package/templates/blog/src/components/icons.tsx +84 -0
- package/templates/blog/src/components/section-heading.tsx +11 -0
- package/templates/blog/src/components/theme-provider.tsx +11 -0
- package/templates/blog/src/components/theme-toggle.tsx +20 -0
- package/templates/blog/src/components/view-all-link.tsx +56 -0
- package/templates/blog/src/config/site.ts +19 -0
- package/templates/blog/src/data/llms.ts +112 -0
- package/templates/blog/src/data/site.ts +52 -0
- package/templates/blog/src/lib/seo.ts +190 -0
- package/templates/blog/src/lib/utils.ts +83 -0
- package/templates/blog/src/styles/globals.css +99 -0
- package/templates/blog/src/utils/cn.ts +7 -0
- package/templates/blog/tsconfig.json +34 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import matter from "gray-matter";
|
|
4
|
+
|
|
5
|
+
type Metadata = {
|
|
6
|
+
title: string;
|
|
7
|
+
publishedAt: string;
|
|
8
|
+
summary: string;
|
|
9
|
+
image?: string;
|
|
10
|
+
repoUrl?: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type BlogPost = {
|
|
14
|
+
metadata: Metadata;
|
|
15
|
+
slug: string;
|
|
16
|
+
content: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function getMDXFiles(dir: string): string[] {
|
|
20
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
21
|
+
|
|
22
|
+
const files = entries
|
|
23
|
+
.filter((entry) => entry.isFile() && path.extname(entry.name) === ".mdx")
|
|
24
|
+
.map((entry) => path.join(dir, entry.name));
|
|
25
|
+
|
|
26
|
+
const folders = entries.filter((entry) => entry.isDirectory());
|
|
27
|
+
|
|
28
|
+
const nestedFiles = folders.flatMap((folder) =>
|
|
29
|
+
getMDXFiles(path.join(dir, folder.name)),
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
return [...files, ...nestedFiles];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function readMDXFile(filePath: string) {
|
|
36
|
+
const rawContent = fs.readFileSync(filePath, "utf-8");
|
|
37
|
+
return matter(rawContent);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function getMDXData(dir: string): BlogPost[] {
|
|
41
|
+
const mdxFiles = getMDXFiles(dir);
|
|
42
|
+
return mdxFiles.map((file) => {
|
|
43
|
+
const { data, content } = readMDXFile(file);
|
|
44
|
+
const slug = path.basename(file, path.extname(file));
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
metadata: data as Metadata,
|
|
48
|
+
slug,
|
|
49
|
+
content,
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function getBlogPosts() {
|
|
55
|
+
return getMDXData(path.join(process.cwd(), "src/blogs/content"));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function getBlogPost(slug: string) {
|
|
59
|
+
const posts = getMDXData(path.join(process.cwd(), "src/blogs/content"));
|
|
60
|
+
|
|
61
|
+
const post = posts.find((p) => p.slug === slug);
|
|
62
|
+
|
|
63
|
+
if (!post) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return post;
|
|
68
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export type Heading = {
|
|
2
|
+
id: string;
|
|
3
|
+
text: string;
|
|
4
|
+
level: 2 | 3;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Extracts h2 and h3 headings from MDX content
|
|
9
|
+
* Creates slug IDs matching rehype-slug behavior
|
|
10
|
+
*/
|
|
11
|
+
export function extractHeadings(content: string): Heading[] {
|
|
12
|
+
const headings: Heading[] = [];
|
|
13
|
+
|
|
14
|
+
// Match ## and ### headings in MDX
|
|
15
|
+
const headingRegex = /^(#{2,3})\s+(.+)$/gm;
|
|
16
|
+
let match;
|
|
17
|
+
|
|
18
|
+
while ((match = headingRegex.exec(content)) !== null) {
|
|
19
|
+
const level = match[1].length as 2 | 3;
|
|
20
|
+
const text = match[2]
|
|
21
|
+
.replace(/\*\*([^*]+)\*\*/g, "$1") // Remove bold
|
|
22
|
+
.replace(/\*([^*]+)\*/g, "$1") // Remove italic
|
|
23
|
+
.replace(/`([^`]+)`/g, "$1") // Remove code blocks
|
|
24
|
+
.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1") // Remove links, keep text
|
|
25
|
+
.trim();
|
|
26
|
+
|
|
27
|
+
// Generate slug matching rehype-slug styles (which seems to map each space to a dash)
|
|
28
|
+
const id = text
|
|
29
|
+
.toLowerCase()
|
|
30
|
+
.replace(/[^\w\s-]/g, "") // Remove special characters
|
|
31
|
+
.replace(/\s/g, "-") // Replace each space with a hyphen (don't squash)
|
|
32
|
+
.replace(/^-|-$/g, ""); // Remove leading/trailing hyphens
|
|
33
|
+
|
|
34
|
+
headings.push({ id, text, level });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return headings;
|
|
38
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { CopyIcon, CheckIcon } from "./icons";
|
|
5
|
+
|
|
6
|
+
interface CopyableCodeProps {
|
|
7
|
+
code: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const CopyableCode = ({ code }: CopyableCodeProps) => {
|
|
11
|
+
const [copied, setCopied] = useState(false);
|
|
12
|
+
|
|
13
|
+
const handleCopy = async () => {
|
|
14
|
+
try {
|
|
15
|
+
await navigator.clipboard.writeText(code);
|
|
16
|
+
setCopied(true);
|
|
17
|
+
setTimeout(() => setCopied(false), 2000);
|
|
18
|
+
} catch (err) {
|
|
19
|
+
console.error("Failed to copy:", err);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div className="relative mx-auto mt-12 max-w-lg rounded-md border border-neutral-200 bg-neutral-200 p-4 pr-12 dark:border-neutral-800 dark:bg-neutral-900">
|
|
25
|
+
<button
|
|
26
|
+
onClick={handleCopy}
|
|
27
|
+
className="absolute top-3 right-3 inline-flex h-8 w-8 items-center justify-center rounded-md text-neutral-600 transition-colors hover:bg-neutral-300 hover:text-neutral-950 dark:text-neutral-400 dark:hover:bg-neutral-800 dark:hover:text-neutral-200"
|
|
28
|
+
aria-label="Copy code"
|
|
29
|
+
>
|
|
30
|
+
{copied ? (
|
|
31
|
+
<CheckIcon className="h-4 w-4" />
|
|
32
|
+
) : (
|
|
33
|
+
<CopyIcon className="h-4 w-4" />
|
|
34
|
+
)}
|
|
35
|
+
</button>
|
|
36
|
+
<code className="block text-xs text-neutral-700 md:text-sm dark:text-neutral-300">
|
|
37
|
+
<span className="text-neutral-400 dark:text-neutral-600">$</span> {code}
|
|
38
|
+
</code>
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import Link from "next/link";
|
|
2
|
+
|
|
3
|
+
export function Footer() {
|
|
4
|
+
return (
|
|
5
|
+
<footer className="border-t border-border py-6">
|
|
6
|
+
<div className="container flex max-w-5xl flex-wrap items-center justify-center gap-4 px-4 mx-auto text-center">
|
|
7
|
+
<Link
|
|
8
|
+
href="/llms.txt"
|
|
9
|
+
className="text-xs text-muted-foreground transition-colors hover:text-foreground"
|
|
10
|
+
>
|
|
11
|
+
llms.txt
|
|
12
|
+
</Link>
|
|
13
|
+
<span className="text-border" aria-hidden>
|
|
14
|
+
·
|
|
15
|
+
</span>
|
|
16
|
+
<Link
|
|
17
|
+
href="/llms-full.txt"
|
|
18
|
+
className="text-xs text-muted-foreground transition-colors hover:text-foreground"
|
|
19
|
+
>
|
|
20
|
+
llms-full.txt
|
|
21
|
+
</Link>
|
|
22
|
+
</div>
|
|
23
|
+
</footer>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import Link from "next/link";
|
|
2
|
+
import { SITE_CONFIG } from "@/config/site";
|
|
3
|
+
import { ThemeToggle } from "@/components/theme-toggle";
|
|
4
|
+
|
|
5
|
+
export function Header() {
|
|
6
|
+
return (
|
|
7
|
+
<header className="sticky top-0 z-50 w-full border-b border-border bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
|
8
|
+
<div className="container flex h-14 max-w-5xl items-center justify-between px-4 mx-auto">
|
|
9
|
+
<Link
|
|
10
|
+
href="/"
|
|
11
|
+
className="text-sm font-semibold tracking-tight text-foreground transition-colors hover:text-foreground/80"
|
|
12
|
+
>
|
|
13
|
+
{SITE_CONFIG.name}
|
|
14
|
+
</Link>
|
|
15
|
+
<nav className="flex items-center gap-4">
|
|
16
|
+
<Link
|
|
17
|
+
href="/blogs"
|
|
18
|
+
className="text-sm font-medium text-muted-foreground transition-colors hover:text-foreground"
|
|
19
|
+
>
|
|
20
|
+
Blog
|
|
21
|
+
</Link>
|
|
22
|
+
<ThemeToggle />
|
|
23
|
+
</nav>
|
|
24
|
+
</div>
|
|
25
|
+
</header>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
export const MoonIcon = ({ className }: { className?: string }) => {
|
|
2
|
+
return (
|
|
3
|
+
<svg
|
|
4
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
+
width="20"
|
|
6
|
+
height="20"
|
|
7
|
+
viewBox="0 0 24 24"
|
|
8
|
+
fill="none"
|
|
9
|
+
stroke="currentColor"
|
|
10
|
+
strokeWidth="2"
|
|
11
|
+
strokeLinecap="round"
|
|
12
|
+
strokeLinejoin="round"
|
|
13
|
+
className={className}
|
|
14
|
+
>
|
|
15
|
+
<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" />
|
|
16
|
+
</svg>
|
|
17
|
+
);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const SunIcon = ({ className }: { className?: string }) => {
|
|
21
|
+
return (
|
|
22
|
+
<svg
|
|
23
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
24
|
+
width="20"
|
|
25
|
+
height="20"
|
|
26
|
+
viewBox="0 0 24 24"
|
|
27
|
+
fill="none"
|
|
28
|
+
stroke="currentColor"
|
|
29
|
+
strokeWidth="2"
|
|
30
|
+
strokeLinecap="round"
|
|
31
|
+
strokeLinejoin="round"
|
|
32
|
+
className={className}
|
|
33
|
+
>
|
|
34
|
+
<circle cx="12" cy="12" r="4" />
|
|
35
|
+
<path d="M12 2v2" />
|
|
36
|
+
<path d="M12 20v2" />
|
|
37
|
+
<path d="m4.93 4.93 1.41 1.41" />
|
|
38
|
+
<path d="m17.66 17.66 1.41 1.41" />
|
|
39
|
+
<path d="M2 12h2" />
|
|
40
|
+
<path d="M20 12h2" />
|
|
41
|
+
<path d="m6.34 17.66-1.41 1.41" />
|
|
42
|
+
<path d="m19.07 4.93-1.41 1.41" />
|
|
43
|
+
</svg>
|
|
44
|
+
);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const CopyIcon = ({ className }: { className?: string }) => {
|
|
48
|
+
return (
|
|
49
|
+
<svg
|
|
50
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
51
|
+
width="16"
|
|
52
|
+
height="16"
|
|
53
|
+
viewBox="0 0 24 24"
|
|
54
|
+
fill="none"
|
|
55
|
+
stroke="currentColor"
|
|
56
|
+
strokeWidth="2"
|
|
57
|
+
strokeLinecap="round"
|
|
58
|
+
strokeLinejoin="round"
|
|
59
|
+
className={className}
|
|
60
|
+
>
|
|
61
|
+
<rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
|
|
62
|
+
<path d="M4 16c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2h8c1.1 0 2 .9 2 2" />
|
|
63
|
+
</svg>
|
|
64
|
+
);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const CheckIcon = ({ className }: { className?: string }) => {
|
|
68
|
+
return (
|
|
69
|
+
<svg
|
|
70
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
71
|
+
width="16"
|
|
72
|
+
height="16"
|
|
73
|
+
viewBox="0 0 24 24"
|
|
74
|
+
fill="none"
|
|
75
|
+
stroke="currentColor"
|
|
76
|
+
strokeWidth="2"
|
|
77
|
+
strokeLinecap="round"
|
|
78
|
+
strokeLinejoin="round"
|
|
79
|
+
className={className}
|
|
80
|
+
>
|
|
81
|
+
<path d="M20 6 9 17l-5-5" />
|
|
82
|
+
</svg>
|
|
83
|
+
);
|
|
84
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { ThemeProvider as NextThemesProvider } from "next-themes";
|
|
4
|
+
import type { ComponentProps } from "react";
|
|
5
|
+
|
|
6
|
+
export function ThemeProvider({
|
|
7
|
+
children,
|
|
8
|
+
...props
|
|
9
|
+
}: ComponentProps<typeof NextThemesProvider>) {
|
|
10
|
+
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useTheme } from "next-themes";
|
|
4
|
+
import { MoonIcon, SunIcon } from "./icons";
|
|
5
|
+
|
|
6
|
+
export const ThemeToggle = () => {
|
|
7
|
+
const { setTheme, resolvedTheme } = useTheme();
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<button
|
|
11
|
+
onClick={() => setTheme(resolvedTheme === "dark" ? "light" : "dark")}
|
|
12
|
+
className="inline-flex h-9 w-9 items-center justify-center rounded-md text-sm font-medium transition-colors hover:bg-zinc-100 focus-visible:ring-1 focus-visible:ring-zinc-950 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50 dark:hover:bg-zinc-800 dark:focus-visible:ring-zinc-300"
|
|
13
|
+
aria-label="Toggle theme"
|
|
14
|
+
>
|
|
15
|
+
<SunIcon className="hidden text-muted-foreground transition-colors hover:text-foreground dark:block" />
|
|
16
|
+
<MoonIcon className="block text-muted-foreground transition-colors hover:text-foreground dark:hidden" />
|
|
17
|
+
<span className="sr-only">Toggle theme</span>
|
|
18
|
+
</button>
|
|
19
|
+
);
|
|
20
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import Link from "next/link";
|
|
4
|
+
import { ArrowUpRight } from "lucide-react";
|
|
5
|
+
import { motion } from "motion/react";
|
|
6
|
+
|
|
7
|
+
interface ViewAllLinkProps {
|
|
8
|
+
href: string;
|
|
9
|
+
children?: React.ReactNode;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const MotionLink = motion.create(Link);
|
|
13
|
+
|
|
14
|
+
export function ViewAllLink({ href, children = "View All" }: ViewAllLinkProps) {
|
|
15
|
+
return (
|
|
16
|
+
<motion.div
|
|
17
|
+
initial={{ opacity: 0, y: 8 }}
|
|
18
|
+
whileInView={{ opacity: 1, y: 0 }}
|
|
19
|
+
viewport={{ once: true, margin: "-40px" }}
|
|
20
|
+
transition={{
|
|
21
|
+
type: "spring",
|
|
22
|
+
stiffness: 400,
|
|
23
|
+
damping: 30,
|
|
24
|
+
}}
|
|
25
|
+
>
|
|
26
|
+
<MotionLink
|
|
27
|
+
href={href}
|
|
28
|
+
className="group inline-flex items-center gap-2 rounded-lg bg-zinc-900 px-5 py-2.5 text-sm font-medium text-white ring-2 ring-zinc-900/10 ring-offset-2 transition-all hover:bg-zinc-800 active:scale-95 dark:bg-zinc-100 dark:text-zinc-900 dark:ring-zinc-100/20 dark:ring-offset-black dark:hover:bg-zinc-200"
|
|
29
|
+
whileHover="hover"
|
|
30
|
+
>
|
|
31
|
+
{children}
|
|
32
|
+
<div className="relative h-4 w-4 overflow-hidden">
|
|
33
|
+
<motion.div
|
|
34
|
+
variants={{
|
|
35
|
+
hover: { x: "150%", y: "-150%" },
|
|
36
|
+
}}
|
|
37
|
+
transition={{ duration: 0.3, ease: "easeInOut" }}
|
|
38
|
+
className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2"
|
|
39
|
+
>
|
|
40
|
+
<ArrowUpRight className="h-4 w-4" />
|
|
41
|
+
</motion.div>
|
|
42
|
+
<motion.div
|
|
43
|
+
initial={{ x: "-150%", y: "150%" }}
|
|
44
|
+
variants={{
|
|
45
|
+
hover: { x: "0%", y: "0%" },
|
|
46
|
+
}}
|
|
47
|
+
transition={{ duration: 0.3, ease: "easeInOut" }}
|
|
48
|
+
className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2"
|
|
49
|
+
>
|
|
50
|
+
<ArrowUpRight className="h-4 w-4" />
|
|
51
|
+
</motion.div>
|
|
52
|
+
</div>
|
|
53
|
+
</MotionLink>
|
|
54
|
+
</motion.div>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import SITE_DATA from "@/data/site";
|
|
2
|
+
|
|
3
|
+
export const SITE_CONFIG = {
|
|
4
|
+
name: SITE_DATA.name,
|
|
5
|
+
url: SITE_DATA.url,
|
|
6
|
+
ogImage: SITE_DATA.ogImage,
|
|
7
|
+
tagline: SITE_DATA.tagline,
|
|
8
|
+
description: SITE_DATA.description,
|
|
9
|
+
shortDescription: SITE_DATA.shortDescription,
|
|
10
|
+
alternateNames: SITE_DATA.alternateNames,
|
|
11
|
+
twitterHandle: SITE_DATA.twitterHandle,
|
|
12
|
+
keywords: SITE_DATA.keywords,
|
|
13
|
+
features: SITE_DATA.features,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const META_THEME_COLORS = {
|
|
17
|
+
light: "#ededed",
|
|
18
|
+
dark: "#09090b",
|
|
19
|
+
};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
const LLMS_DATA = {
|
|
2
|
+
// Quick start command for cloning the starter
|
|
3
|
+
quickStart: "bunx degit AudoraLabs/next-starter my-app",
|
|
4
|
+
|
|
5
|
+
// GitHub repository URL
|
|
6
|
+
repository: "https://github.com/AudoraLabs/next-starter",
|
|
7
|
+
|
|
8
|
+
// Tech stack summary
|
|
9
|
+
techStack: [
|
|
10
|
+
"Next.js 16 with App Router",
|
|
11
|
+
"React 19",
|
|
12
|
+
"TypeScript (strict mode)",
|
|
13
|
+
"Tailwind CSS 4",
|
|
14
|
+
"Bun (package manager)",
|
|
15
|
+
"ESLint 9 (flat config)",
|
|
16
|
+
"Prettier",
|
|
17
|
+
"React Compiler",
|
|
18
|
+
],
|
|
19
|
+
|
|
20
|
+
// Key features for the summary
|
|
21
|
+
features: [
|
|
22
|
+
"SEO-ready with robots.ts, sitemap.ts, and Open Graph metadata",
|
|
23
|
+
"Title templates and metadataBase configured",
|
|
24
|
+
"Path alias @/* mapped to ./src/*",
|
|
25
|
+
"Dark mode support via CSS custom properties",
|
|
26
|
+
"Geist font family pre-configured",
|
|
27
|
+
"Pre-commit hooks with Husky and lint-staged",
|
|
28
|
+
],
|
|
29
|
+
|
|
30
|
+
// Folder structure for llms-full.txt
|
|
31
|
+
folderStructure: `src/
|
|
32
|
+
├── app/
|
|
33
|
+
│ ├── layout.tsx # Root layout with metadata
|
|
34
|
+
│ ├── page.tsx # Home page
|
|
35
|
+
│ ├── manifest.ts # PWA manifest
|
|
36
|
+
│ ├── robots.ts # robots.txt generation
|
|
37
|
+
│ ├── sitemap.ts # Sitemap generation
|
|
38
|
+
│ ├── llms.txt/ # AI-friendly summary (this file)
|
|
39
|
+
│ └── llms-full.txt/ # AI-friendly full documentation
|
|
40
|
+
├── components/
|
|
41
|
+
│ ├── icons.tsx # Icon components
|
|
42
|
+
│ ├── theme-provider.tsx # Theme context provider
|
|
43
|
+
│ └── theme-toggle.tsx # Dark/light mode toggle
|
|
44
|
+
├── config/
|
|
45
|
+
│ └── site.ts # Site configuration exports
|
|
46
|
+
├── data/
|
|
47
|
+
│ ├── site.ts # Site data (name, URL, description)
|
|
48
|
+
│ └── llms.ts # LLMs.txt content configuration
|
|
49
|
+
├── lib/
|
|
50
|
+
│ └── seo.ts # SEO utilities and JSON-LD generators
|
|
51
|
+
├── styles/
|
|
52
|
+
│ └── globals.css # Global styles and Tailwind imports
|
|
53
|
+
└── utils/
|
|
54
|
+
└── cn.ts # Class name utility`,
|
|
55
|
+
|
|
56
|
+
// Development commands
|
|
57
|
+
commands: {
|
|
58
|
+
dev: "bun dev",
|
|
59
|
+
build: "bun run build",
|
|
60
|
+
start: "bun start",
|
|
61
|
+
lint: "bun lint",
|
|
62
|
+
format: "bun format",
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
// SEO documentation for llms-full.txt
|
|
66
|
+
seoDetails: `## SEO Configuration
|
|
67
|
+
|
|
68
|
+
This starter includes production-ready SEO defaults:
|
|
69
|
+
|
|
70
|
+
### Metadata (src/lib/seo.ts)
|
|
71
|
+
- getMetadata(): Default site metadata with Open Graph and Twitter cards
|
|
72
|
+
- getPageMetadata(): Page-specific metadata generator
|
|
73
|
+
- getViewport(): Viewport configuration with theme colors
|
|
74
|
+
|
|
75
|
+
### Structured Data (JSON-LD)
|
|
76
|
+
- getWebSiteJsonLd(): WebSite schema
|
|
77
|
+
- getOrganizationJsonLd(): Organization schema
|
|
78
|
+
- getBreadcrumbJsonLd(): Breadcrumb navigation schema
|
|
79
|
+
|
|
80
|
+
### Configuration (src/data/site.ts)
|
|
81
|
+
Customize these values for your project:
|
|
82
|
+
- name: Site name
|
|
83
|
+
- url: Base URL for canonical links
|
|
84
|
+
- ogImage: Default Open Graph image
|
|
85
|
+
- tagline: Short tagline
|
|
86
|
+
- description: Full description
|
|
87
|
+
- twitterHandle: Twitter/X handle
|
|
88
|
+
|
|
89
|
+
### Files
|
|
90
|
+
- robots.ts: Generates robots.txt with sitemap reference
|
|
91
|
+
- sitemap.ts: Dynamic sitemap generation
|
|
92
|
+
- manifest.ts: PWA web app manifest`,
|
|
93
|
+
|
|
94
|
+
// Conventions documentation
|
|
95
|
+
conventions: `## Conventions
|
|
96
|
+
|
|
97
|
+
### Path Aliases
|
|
98
|
+
Use @/* to import from src/*:
|
|
99
|
+
- import { cn } from "@/utils/cn"
|
|
100
|
+
- import { SITE_CONFIG } from "@/config/site"
|
|
101
|
+
|
|
102
|
+
### Theme
|
|
103
|
+
Dark mode uses CSS custom properties. Toggle with ThemeProvider and ThemeToggle components.
|
|
104
|
+
|
|
105
|
+
### Components
|
|
106
|
+
No component library included by default. Add shadcn/ui or Radix as needed.
|
|
107
|
+
|
|
108
|
+
### Styling
|
|
109
|
+
Tailwind CSS 4 with PostCSS. Global styles in src/styles/globals.css.`,
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export default LLMS_DATA;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const SITE_DATA = {
|
|
2
|
+
name: "next-starter",
|
|
3
|
+
url: process.env.NEXT_PUBLIC_SITE_URL ?? "http://localhost:3000",
|
|
4
|
+
ogImage:
|
|
5
|
+
process.env.NEXT_PUBLIC_OG_IMAGE ?? "/images/screenshot-desktop-light.webp",
|
|
6
|
+
|
|
7
|
+
tagline: "Kickstart your Next.js project",
|
|
8
|
+
description: "A modern, performant starter kit for Next.js applications.",
|
|
9
|
+
shortDescription:
|
|
10
|
+
"A modern, performant starter kit for Next.js applications.",
|
|
11
|
+
|
|
12
|
+
alternateNames: ["next-starter", "Next Starter", "NextJS Starter"],
|
|
13
|
+
|
|
14
|
+
twitterHandle: "@nextstarter",
|
|
15
|
+
|
|
16
|
+
keywords: [
|
|
17
|
+
"next-starter",
|
|
18
|
+
"next.js starter",
|
|
19
|
+
"react boilerplate",
|
|
20
|
+
"next.js boilerplate",
|
|
21
|
+
"nextjs template",
|
|
22
|
+
"starter project",
|
|
23
|
+
"next.js template",
|
|
24
|
+
"typescript nextjs starter",
|
|
25
|
+
"boilerplate for next.js",
|
|
26
|
+
"web app starter",
|
|
27
|
+
],
|
|
28
|
+
|
|
29
|
+
features: [
|
|
30
|
+
{
|
|
31
|
+
title: "MDX blog",
|
|
32
|
+
description:
|
|
33
|
+
"Write posts in MDX with frontmatter, code blocks, and components.",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
title: "SEO & metadata",
|
|
37
|
+
description:
|
|
38
|
+
"Built-in metadata, Open Graph, and JSON-LD for search and sharing.",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
title: "Dark mode",
|
|
42
|
+
description: "System-aware theme toggle with persistent preference.",
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
title: "TypeScript",
|
|
46
|
+
description:
|
|
47
|
+
"Full TypeScript support and strict mode for safer refactors.",
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export default SITE_DATA;
|