mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
add nextjs frontend and backend
This commit is contained in:
@@ -51,13 +51,14 @@ export async function setupEnvironmentVariables(
|
||||
const hasReactRouter = options.frontend.includes("react-router");
|
||||
const hasTanStackRouter = options.frontend.includes("tanstack-router");
|
||||
const hasTanStackStart = options.frontend.includes("tanstack-start");
|
||||
const hasNextJs = options.frontend.includes("next");
|
||||
const hasWebFrontend =
|
||||
hasReactRouter || hasTanStackRouter || hasTanStackStart;
|
||||
hasReactRouter || hasTanStackRouter || hasTanStackStart || hasNextJs;
|
||||
|
||||
let corsOrigin = "http://localhost:3000";
|
||||
if (hasReactRouter) {
|
||||
corsOrigin = "http://localhost:5173";
|
||||
} else if (hasTanStackRouter || hasTanStackStart) {
|
||||
} else if (hasTanStackRouter || hasTanStackStart || hasNextJs) {
|
||||
corsOrigin = "http://localhost:3001";
|
||||
}
|
||||
|
||||
@@ -114,9 +115,15 @@ export async function setupEnvironmentVariables(
|
||||
|
||||
if (hasWebFrontend) {
|
||||
const clientDir = path.join(projectDir, "apps/web");
|
||||
let envVarName = "VITE_SERVER_URL";
|
||||
|
||||
if (hasNextJs) {
|
||||
envVarName = "NEXT_PUBLIC_SERVER_URL";
|
||||
}
|
||||
|
||||
const clientVars: EnvVariable[] = [
|
||||
{
|
||||
key: "VITE_SERVER_URL",
|
||||
key: envVarName,
|
||||
value: "http://localhost:3000",
|
||||
condition: true,
|
||||
},
|
||||
|
||||
@@ -8,6 +8,10 @@ export async function setupRuntime(
|
||||
runtime: ProjectRuntime,
|
||||
backendFramework: ProjectBackend,
|
||||
): Promise<void> {
|
||||
if (backendFramework === "next") {
|
||||
return;
|
||||
}
|
||||
|
||||
const serverDir = path.join(projectDir, "apps/server");
|
||||
const serverIndexPath = path.join(serverDir, "src/index.ts");
|
||||
|
||||
|
||||
@@ -51,9 +51,10 @@ export async function setupFrontendTemplates(
|
||||
const hasTanstackWeb = frontends.includes("tanstack-router");
|
||||
const hasTanstackStart = frontends.includes("tanstack-start");
|
||||
const hasReactRouterWeb = frontends.includes("react-router");
|
||||
const hasNextWeb = frontends.includes("next");
|
||||
const hasNative = frontends.includes("native");
|
||||
|
||||
if (hasTanstackWeb || hasReactRouterWeb || hasTanstackStart) {
|
||||
if (hasTanstackWeb || hasReactRouterWeb || hasTanstackStart || hasNextWeb) {
|
||||
const webDir = path.join(projectDir, "apps/web");
|
||||
await fs.ensureDir(webDir);
|
||||
|
||||
@@ -62,20 +63,35 @@ export async function setupFrontendTemplates(
|
||||
await fs.copy(webBaseDir, webDir);
|
||||
}
|
||||
|
||||
let frameworkName = "web-react-router";
|
||||
if (hasTanstackWeb) {
|
||||
frameworkName = "web-tanstack-router";
|
||||
const frameworkDir = path.join(
|
||||
PKG_ROOT,
|
||||
"template/base/apps/web-tanstack-router",
|
||||
);
|
||||
if (await fs.pathExists(frameworkDir)) {
|
||||
await fs.copy(frameworkDir, webDir, { overwrite: true });
|
||||
}
|
||||
} else if (hasTanstackStart) {
|
||||
frameworkName = "web-tanstack-start";
|
||||
}
|
||||
|
||||
const webFrameworkDir = path.join(
|
||||
PKG_ROOT,
|
||||
`template/base/apps/${frameworkName}`,
|
||||
);
|
||||
|
||||
if (await fs.pathExists(webFrameworkDir)) {
|
||||
await fs.copy(webFrameworkDir, webDir, { overwrite: true });
|
||||
const frameworkDir = path.join(
|
||||
PKG_ROOT,
|
||||
"template/base/apps/web-tanstack-start",
|
||||
);
|
||||
if (await fs.pathExists(frameworkDir)) {
|
||||
await fs.copy(frameworkDir, webDir, { overwrite: true });
|
||||
}
|
||||
} else if (hasReactRouterWeb) {
|
||||
const frameworkDir = path.join(
|
||||
PKG_ROOT,
|
||||
"template/base/apps/web-react-router",
|
||||
);
|
||||
if (await fs.pathExists(frameworkDir)) {
|
||||
await fs.copy(frameworkDir, webDir, { overwrite: true });
|
||||
}
|
||||
} else if (hasNextWeb) {
|
||||
const frameworkDir = path.join(PKG_ROOT, "template/base/apps/web-next");
|
||||
if (await fs.pathExists(frameworkDir)) {
|
||||
await fs.copy(frameworkDir, webDir, { overwrite: true });
|
||||
}
|
||||
}
|
||||
|
||||
const packageJsonPath = path.join(webDir, "package.json");
|
||||
@@ -105,6 +121,28 @@ export async function setupBackendFramework(
|
||||
projectDir: string,
|
||||
framework: ProjectBackend,
|
||||
): Promise<void> {
|
||||
if (framework === "next") {
|
||||
const serverDir = path.join(projectDir, "apps/server");
|
||||
const nextTemplateDir = path.join(
|
||||
PKG_ROOT,
|
||||
"template/with-next/apps/server",
|
||||
);
|
||||
|
||||
await fs.ensureDir(serverDir);
|
||||
|
||||
if (await fs.pathExists(nextTemplateDir)) {
|
||||
await fs.copy(nextTemplateDir, serverDir, { overwrite: true });
|
||||
|
||||
const packageJsonPath = path.join(serverDir, "package.json");
|
||||
if (await fs.pathExists(packageJsonPath)) {
|
||||
const packageJson = await fs.readJson(packageJsonPath);
|
||||
packageJson.name = "server";
|
||||
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const frameworkDir = path.join(PKG_ROOT, `template/with-${framework}`);
|
||||
if (await fs.pathExists(frameworkDir)) {
|
||||
await fs.copy(frameworkDir, projectDir, { overwrite: true });
|
||||
@@ -161,8 +199,14 @@ export async function setupAuthTemplate(
|
||||
const hasReactRouter = frontends.includes("react-router");
|
||||
const hasTanStackRouter = frontends.includes("tanstack-router");
|
||||
const hasTanStackStart = frontends.includes("tanstack-start");
|
||||
const hasNextRouter = frontends.includes("next");
|
||||
|
||||
if (hasReactRouter || hasTanStackRouter || hasTanStackStart) {
|
||||
if (
|
||||
hasReactRouter ||
|
||||
hasTanStackRouter ||
|
||||
hasTanStackStart ||
|
||||
hasNextRouter
|
||||
) {
|
||||
const webDir = path.join(projectDir, "apps/web");
|
||||
|
||||
const webBaseAuthDir = path.join(authTemplateDir, "apps/web-base");
|
||||
@@ -199,6 +243,13 @@ export async function setupAuthTemplate(
|
||||
await fs.copy(tanstackStartAuthDir, webDir, { overwrite: true });
|
||||
}
|
||||
}
|
||||
|
||||
if (hasNextRouter) {
|
||||
const nextAuthDir = path.join(authTemplateDir, "apps/web-next");
|
||||
if (await fs.pathExists(nextAuthDir)) {
|
||||
await fs.copy(nextAuthDir, webDir, { overwrite: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const serverAuthDir = path.join(authTemplateDir, "apps/server/src");
|
||||
@@ -216,30 +267,73 @@ export async function setupAuthTemplate(
|
||||
{ overwrite: true },
|
||||
);
|
||||
|
||||
const contextFileName = `with-${framework}-context.ts`;
|
||||
await fs.copy(
|
||||
path.join(serverAuthDir, "lib", contextFileName),
|
||||
path.join(projectServerDir, "lib/context.ts"),
|
||||
{ overwrite: true },
|
||||
);
|
||||
|
||||
const indexFileName = `with-${framework}-index.ts`;
|
||||
await fs.copy(
|
||||
path.join(serverAuthDir, indexFileName),
|
||||
path.join(projectServerDir, "index.ts"),
|
||||
{ overwrite: true },
|
||||
);
|
||||
|
||||
const authLibFileName = getAuthLibDir(orm, database);
|
||||
const authLibSourceDir = path.join(serverAuthDir, authLibFileName);
|
||||
if (await fs.pathExists(authLibSourceDir)) {
|
||||
const files = await fs.readdir(authLibSourceDir);
|
||||
for (const file of files) {
|
||||
await fs.copy(
|
||||
path.join(authLibSourceDir, file),
|
||||
path.join(projectServerDir, "lib", file),
|
||||
{ overwrite: true },
|
||||
if (framework === "next") {
|
||||
if (
|
||||
await fs.pathExists(
|
||||
path.join(authTemplateDir, "apps/server/src/with-next-app"),
|
||||
)
|
||||
) {
|
||||
const nextAppAuthDir = path.join(
|
||||
authTemplateDir,
|
||||
"apps/server/src/with-next-app",
|
||||
);
|
||||
const nextAppDestDir = path.join(projectDir, "apps/server/src/app");
|
||||
|
||||
await fs.ensureDir(nextAppDestDir);
|
||||
|
||||
const files = await fs.readdir(nextAppAuthDir);
|
||||
for (const file of files) {
|
||||
const srcPath = path.join(nextAppAuthDir, file);
|
||||
const destPath = path.join(nextAppDestDir, file);
|
||||
await fs.copy(srcPath, destPath, { overwrite: true });
|
||||
}
|
||||
}
|
||||
|
||||
const contextFileName = "with-next-context.ts";
|
||||
await fs.copy(
|
||||
path.join(serverAuthDir, "lib", contextFileName),
|
||||
path.join(projectServerDir, "lib/context.ts"),
|
||||
{ overwrite: true },
|
||||
);
|
||||
|
||||
const authLibFileName = getAuthLibDir(orm, database);
|
||||
const authLibSourceDir = path.join(serverAuthDir, authLibFileName);
|
||||
if (await fs.pathExists(authLibSourceDir)) {
|
||||
const files = await fs.readdir(authLibSourceDir);
|
||||
for (const file of files) {
|
||||
await fs.copy(
|
||||
path.join(authLibSourceDir, file),
|
||||
path.join(projectServerDir, "lib", file),
|
||||
{ overwrite: true },
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const contextFileName = `with-${framework}-context.ts`;
|
||||
await fs.copy(
|
||||
path.join(serverAuthDir, "lib", contextFileName),
|
||||
path.join(projectServerDir, "lib/context.ts"),
|
||||
{ overwrite: true },
|
||||
);
|
||||
|
||||
const indexFileName = `with-${framework}-index.ts`;
|
||||
await fs.copy(
|
||||
path.join(serverAuthDir, indexFileName),
|
||||
path.join(projectServerDir, "index.ts"),
|
||||
{ overwrite: true },
|
||||
);
|
||||
|
||||
const authLibFileName = getAuthLibDir(orm, database);
|
||||
const authLibSourceDir = path.join(serverAuthDir, authLibFileName);
|
||||
if (await fs.pathExists(authLibSourceDir)) {
|
||||
const files = await fs.readdir(authLibSourceDir);
|
||||
for (const file of files) {
|
||||
await fs.copy(
|
||||
path.join(authLibSourceDir, file),
|
||||
path.join(projectServerDir, "lib", file),
|
||||
{ overwrite: true },
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -90,6 +90,7 @@ async function main() {
|
||||
"tanstack-router",
|
||||
"react-router",
|
||||
"tanstack-start",
|
||||
"next",
|
||||
"native",
|
||||
"none",
|
||||
],
|
||||
@@ -128,7 +129,7 @@ async function main() {
|
||||
.option("backend", {
|
||||
type: "string",
|
||||
describe: "Backend framework",
|
||||
choices: ["hono", "express", "elysia"],
|
||||
choices: ["hono", "express", "next", "elysia"],
|
||||
})
|
||||
.option("runtime", {
|
||||
type: "string",
|
||||
|
||||
@@ -16,6 +16,11 @@ export async function getBackendFrameworkChoice(
|
||||
label: "Hono",
|
||||
hint: "Lightweight, ultrafast web framework",
|
||||
},
|
||||
{
|
||||
value: "next",
|
||||
label: "Next.js",
|
||||
hint: "Full-stack framework with API routes",
|
||||
},
|
||||
{
|
||||
value: "express",
|
||||
label: "Express",
|
||||
|
||||
@@ -52,7 +52,8 @@ export async function gatherConfig(
|
||||
},
|
||||
frontend: () => getFrontendChoice(flags.frontend),
|
||||
backend: () => getBackendFrameworkChoice(flags.backend),
|
||||
runtime: () => getRuntimeChoice(flags.runtime),
|
||||
runtime: ({ results }) =>
|
||||
getRuntimeChoice(flags.runtime, results.backend),
|
||||
database: () => getDatabaseChoice(flags.database),
|
||||
orm: ({ results }) =>
|
||||
getORMChoice(flags.orm, results.database !== "none", results.database),
|
||||
|
||||
@@ -27,7 +27,8 @@ export async function getFrontendChoice(
|
||||
(f) =>
|
||||
f === "tanstack-router" ||
|
||||
f === "react-router" ||
|
||||
f === "tanstack-start",
|
||||
f === "tanstack-start" ||
|
||||
f === "next",
|
||||
)
|
||||
? ["web"]
|
||||
: [],
|
||||
@@ -54,6 +55,11 @@ export async function getFrontendChoice(
|
||||
label: "React Router",
|
||||
hint: "A user‑obsessed, standards‑focused, multi‑strategy router",
|
||||
},
|
||||
{
|
||||
value: "next",
|
||||
label: "Next.js",
|
||||
hint: "The React Framework for the Web",
|
||||
},
|
||||
{
|
||||
value: "tanstack-start",
|
||||
label: "TanStack Start (beta)",
|
||||
@@ -65,7 +71,8 @@ export async function getFrontendChoice(
|
||||
(f) =>
|
||||
f === "tanstack-router" ||
|
||||
f === "react-router" ||
|
||||
f === "tanstack-start",
|
||||
f === "tanstack-start" ||
|
||||
f === "next",
|
||||
) || "tanstack-router",
|
||||
});
|
||||
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
import { cancel, isCancel, select } from "@clack/prompts";
|
||||
import pc from "picocolors";
|
||||
import { DEFAULT_CONFIG } from "../constants";
|
||||
import type { ProjectRuntime } from "../types";
|
||||
import type { ProjectBackend, ProjectRuntime } from "../types";
|
||||
|
||||
export async function getRuntimeChoice(
|
||||
runtime?: ProjectRuntime,
|
||||
backend?: ProjectBackend,
|
||||
): Promise<ProjectRuntime> {
|
||||
if (runtime !== undefined) return runtime;
|
||||
|
||||
if (backend === "next") {
|
||||
return "node";
|
||||
}
|
||||
|
||||
const response = await select<ProjectRuntime>({
|
||||
message: "Select runtime",
|
||||
options: [
|
||||
|
||||
@@ -13,13 +13,14 @@ export type ProjectAddons =
|
||||
| "husky"
|
||||
| "starlight"
|
||||
| "none";
|
||||
export type ProjectBackend = "hono" | "elysia" | "express";
|
||||
export type ProjectBackend = "hono" | "elysia" | "express" | "next";
|
||||
export type ProjectRuntime = "node" | "bun";
|
||||
export type ProjectExamples = "todo" | "ai" | "none";
|
||||
export type ProjectFrontend =
|
||||
| "react-router"
|
||||
| "tanstack-router"
|
||||
| "tanstack-start"
|
||||
| "next"
|
||||
| "native"
|
||||
| "none";
|
||||
export type ProjectDBSetup =
|
||||
|
||||
Reference in New Issue
Block a user