feat: remove server and update deps to static
This commit is contained in:
parent
92bd962b14
commit
ed3f252ea7
21 changed files with 362 additions and 889 deletions
38
README.md
38
README.md
|
|
@ -8,7 +8,7 @@ Jet Pham's personal website. This website comes with a long story. The domain wa
|
|||
|
||||
The site originally contained a blog. It was made in Next.js plainly with plain colors and no real style. I posted a few blogs about my life but eventually lost motivaiton and didn't like sharing it with other people after having been inspired by so many other cool websites.
|
||||
|
||||
I started to become more obsessed with Rust and rewrote my website from being a blog into a static linktree site made in rust via WASM. It was in ASCII style using a modified fork of ratatui and had a fun implementation of Conways Game of Life in the background.
|
||||
I started to become more obsessed with Rust and rewrote my website from being a blog into a static linktree site made in rust via WASM. It was in ASCII style using a modified fork of ratzilla and had a fun implementation of Conways Game of Life in the background.
|
||||
|
||||
After leaving that website alone, I started to make more web based projects in Next.js. I realized I could properly make this website awesome and still keep the interesting style in the site while making it more performant, responsive, and accessible. This is the state that you see the website in now.
|
||||
|
||||
|
|
@ -23,12 +23,9 @@ Let me know if you have any feedback about the site!
|
|||
|
||||
## Tech Stack
|
||||
|
||||
- [Next.js 15](https://nextjs.org) with Turbo mode
|
||||
- [Prisma](https://prisma.io) with PostgreSQL
|
||||
- [Next.js 16](https://nextjs.org) with Turbo mode
|
||||
- [Tailwind CSS v4](https://tailwindcss.com)
|
||||
- [tRPC](https://trpc.io)
|
||||
- [TypeScript](https://www.typescriptlang.org/)
|
||||
- [React Query](https://tanstack.com/query)
|
||||
- [React 19](https://react.dev/)
|
||||
- Rust + WebAssembly (for Conway's Game of Life)
|
||||
- [Bun](https://bun.sh) (package manager)
|
||||
|
|
@ -38,9 +35,8 @@ Let me know if you have any feedback about the site!
|
|||
### Prerequisites
|
||||
|
||||
- Bun
|
||||
- Docker (or Podman)
|
||||
- Rust (for building the Conway's Game of Life WASM module)
|
||||
- wasm-pack (install via `curl https://drager.github.io/wasm-pack/installer/init.sh -sSf | sh` or use the install script)
|
||||
- wasm-pack (or use the install script)
|
||||
|
||||
### Getting Started
|
||||
|
||||
|
|
@ -52,7 +48,7 @@ Let me know if you have any feedback about the site!
|
|||
bun run build:wasm
|
||||
```
|
||||
|
||||
Or use the install script which will also install wasm-pack if needed:
|
||||
Or use the install script:
|
||||
|
||||
```bash
|
||||
./install.sh
|
||||
|
|
@ -64,30 +60,7 @@ Let me know if you have any feedback about the site!
|
|||
bun install
|
||||
```
|
||||
|
||||
4. Set up environment variables:
|
||||
|
||||
```bash
|
||||
cp .env.example .env.local
|
||||
# Edit .env.local with your configuration
|
||||
```
|
||||
|
||||
Adjust the database URL as needed for your setup.
|
||||
|
||||
5. Start the database:
|
||||
|
||||
```bash
|
||||
./start-database.sh
|
||||
```
|
||||
|
||||
This script will start a PostgreSQL database in a Docker or Podman container. Make sure Docker or Podman is installed and running.
|
||||
|
||||
6. Set up the database schema:
|
||||
|
||||
```bash
|
||||
bun run db:push
|
||||
```
|
||||
|
||||
7. Start the development server:
|
||||
4. Start the development server:
|
||||
|
||||
```bash
|
||||
bun run dev
|
||||
|
|
@ -100,4 +73,5 @@ The site will be available at `http://localhost:3000`.
|
|||
```
|
||||
src/ - Next.js app router pages
|
||||
cgol/ - Rust WASM module for Conway's Game of Life
|
||||
public/ - Static assets
|
||||
```
|
||||
|
|
|
|||
2
check_cleanup.txt
Normal file
2
check_cleanup.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
$ bun run lint && tsc --noEmit
|
||||
$ eslint .
|
||||
|
|
@ -7,9 +7,8 @@ const compat = new FlatCompat({
|
|||
|
||||
export default tseslint.config(
|
||||
{
|
||||
ignores: ['.next']
|
||||
ignores: ['.next', 'cgol/pkg/**/*', 'next-env.d.ts']
|
||||
},
|
||||
...compat.extends("next/core-web-vitals"),
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx'],
|
||||
extends: [
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially useful
|
||||
* for Docker builds.
|
||||
*/
|
||||
import "./src/env.js";
|
||||
|
||||
|
||||
/** @type {import("next").NextConfig} */
|
||||
const config = {
|
||||
|
|
|
|||
54
package.json
54
package.json
|
|
@ -6,56 +6,48 @@
|
|||
"scripts": {
|
||||
"build": "next build",
|
||||
"build:wasm": "cd cgol && wasm-pack build --release --target web",
|
||||
"check": "next lint && tsc --noEmit",
|
||||
"db:generate": "prisma migrate dev",
|
||||
"db:migrate": "prisma migrate deploy",
|
||||
"db:push": "prisma db push",
|
||||
"db:studio": "prisma studio",
|
||||
"check": "bun run lint && tsc --noEmit",
|
||||
"dev": "next dev --turbo",
|
||||
"format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,mdx}\" --cache",
|
||||
"format:write": "prettier --write \"**/*.{ts,tsx,js,jsx,mdx}\" --cache",
|
||||
"postinstall": "prisma generate",
|
||||
"lint": "next lint",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "next lint --fix",
|
||||
"preview": "next build && next start",
|
||||
"start": "next start",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@prisma/client": "^6.19.0",
|
||||
"@t3-oss/env-nextjs": "^0.12.0",
|
||||
"@tanstack/react-query": "^5.90.11",
|
||||
"@trpc/client": "^11.7.2",
|
||||
"@trpc/react-query": "^11.7.2",
|
||||
"@trpc/server": "^11.7.2",
|
||||
"anser": "^2.3.3",
|
||||
"@tanstack/react-query": "^5.90.21",
|
||||
"anser": "^2.3.5",
|
||||
"cgol": "file:./cgol/pkg",
|
||||
"escape-carriage": "^1.3.1",
|
||||
"next": "^15.5.6",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"next": "^16.1.6",
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
"server-only": "^0.0.1",
|
||||
"superjson": "^2.2.5",
|
||||
"zod": "^3.25.76"
|
||||
"superjson": "^2.2.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.3.1",
|
||||
"@tailwindcss/postcss": "^4.1.17",
|
||||
"@types/node": "^20.19.25",
|
||||
"@types/react": "^19.2.7",
|
||||
"@eslint/eslintrc": "^3.3.3",
|
||||
"@next/eslint-plugin-next": "^16.1.6",
|
||||
"@tailwindcss/postcss": "^4.2.0",
|
||||
"@types/node": "^25.3.0",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint-config-next": "^15.5.6",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "^16.1.6",
|
||||
"postcss": "^8.5.6",
|
||||
"prettier": "^3.6.2",
|
||||
"prettier-plugin-tailwindcss": "^0.6.14",
|
||||
"prisma": "^6.19.0",
|
||||
"prettier": "^3.8.1",
|
||||
"prettier-plugin-tailwindcss": "^0.7.2",
|
||||
"raw-loader": "^4.0.2",
|
||||
"tailwindcss": "^4.1.17",
|
||||
"react-doctor": "^0.0.21",
|
||||
"tailwindcss": "^4.2.0",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.48.0"
|
||||
"typescript-eslint": "^8.56.0",
|
||||
"webpack": "^5.105.2"
|
||||
},
|
||||
"ct3aMetadata": {
|
||||
"initVersion": "7.40.0"
|
||||
}
|
||||
},
|
||||
"overrides": {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
// This is your Prisma schema file,
|
||||
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
output = "../generated/prisma"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model Post {
|
||||
id Int @id @default(autoincrement())
|
||||
name String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([name])
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
import { api } from "~/trpc/react";
|
||||
|
||||
export function LatestPost() {
|
||||
const [latestPost] = api.post.getLatest.useSuspenseQuery();
|
||||
|
||||
const utils = api.useUtils();
|
||||
const [name, setName] = useState("");
|
||||
const createPost = api.post.create.useMutation({
|
||||
onSuccess: async () => {
|
||||
await utils.post.invalidate();
|
||||
setName("");
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-xs">
|
||||
{latestPost ? (
|
||||
<p className="truncate">Your most recent post: {latestPost.name}</p>
|
||||
) : (
|
||||
<p>You have no posts yet.</p>
|
||||
)}
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
createPost.mutate({ name });
|
||||
}}
|
||||
className="flex flex-col gap-2"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Title"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
className="w-full rounded-full bg-white/10 px-4 py-2 text-white"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
className="rounded-full bg-white/10 px-10 py-3 font-semibold transition hover:bg-white/20"
|
||||
disabled={createPost.isPending}
|
||||
>
|
||||
{createPost.isPending ? "Submitting..." : "Submit"}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
|
||||
import { type NextRequest } from "next/server";
|
||||
|
||||
import { env } from "~/env";
|
||||
import { appRouter } from "~/server/api/root";
|
||||
import { createTRPCContext } from "~/server/api/trpc";
|
||||
|
||||
/**
|
||||
* This wraps the `createTRPCContext` helper and provides the required context for the tRPC API when
|
||||
* handling a HTTP request (e.g. when you make requests from Client Components).
|
||||
*/
|
||||
const createContext = async (req: NextRequest) => {
|
||||
return createTRPCContext({
|
||||
headers: req.headers,
|
||||
});
|
||||
};
|
||||
|
||||
const handler = (req: NextRequest) =>
|
||||
fetchRequestHandler({
|
||||
endpoint: "/api/trpc",
|
||||
req,
|
||||
router: appRouter,
|
||||
createContext: () => createContext(req),
|
||||
onError:
|
||||
env.NODE_ENV === "development"
|
||||
? ({ path, error }) => {
|
||||
console.error(
|
||||
`❌ tRPC failed on ${path ?? "<no-path>"}: ${error.message}`
|
||||
);
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
|
||||
export { handler as GET, handler as POST };
|
||||
|
|
@ -2,12 +2,9 @@ import "~/styles/globals.css";
|
|||
|
||||
import { type Metadata, type Viewport } from "next";
|
||||
|
||||
// import { TRPCReactProvider } from "~/trpc/react";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Jet Pham",
|
||||
description: "Jet Pham's personal website",
|
||||
icons: [{ rel: "icon", url: "/favicon.ico" }],
|
||||
appleWebApp: {
|
||||
title: "Jet Pham",
|
||||
},
|
||||
|
|
@ -25,9 +22,7 @@ export default function RootLayout({
|
|||
return (
|
||||
<html lang="en">
|
||||
<body>
|
||||
{/* <TRPCReactProvider> */}
|
||||
{children}
|
||||
{/* </TRPCReactProvider> */}
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
// import { HydrateClient } from "~/trpc/server";
|
||||
import { BorderedBox } from "./_components/bordered-box";
|
||||
import { FrostedBox } from "./_components/frosted-box";
|
||||
import Header from "./_components/header";
|
||||
|
|
@ -9,7 +8,6 @@ import FirstName from "~/assets/Jet.txt";
|
|||
|
||||
export default async function Home() {
|
||||
return (
|
||||
// <HydrateClient>
|
||||
<>
|
||||
<CgolCanvas />
|
||||
<main>
|
||||
|
|
@ -74,6 +72,5 @@ export default async function Home() {
|
|||
</div>
|
||||
</main>
|
||||
</>
|
||||
// </HydrateClient>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
44
src/env.js
44
src/env.js
|
|
@ -1,44 +0,0 @@
|
|||
import { createEnv } from "@t3-oss/env-nextjs";
|
||||
import { z } from "zod";
|
||||
|
||||
export const env = createEnv({
|
||||
/**
|
||||
* Specify your server-side environment variables schema here. This way you can ensure the app
|
||||
* isn't built with invalid env vars.
|
||||
*/
|
||||
server: {
|
||||
DATABASE_URL: z.string().url(),
|
||||
NODE_ENV: z
|
||||
.enum(["development", "test", "production"])
|
||||
.default("development"),
|
||||
},
|
||||
|
||||
/**
|
||||
* Specify your client-side environment variables schema here. This way you can ensure the app
|
||||
* isn't built with invalid env vars. To expose them to the client, prefix them with
|
||||
* `NEXT_PUBLIC_`.
|
||||
*/
|
||||
client: {
|
||||
// NEXT_PUBLIC_CLIENTVAR: z.string(),
|
||||
},
|
||||
|
||||
/**
|
||||
* You can't destruct `process.env` as a regular object in the Next.js edge runtimes (e.g.
|
||||
* middlewares) or client-side so we need to destruct manually.
|
||||
*/
|
||||
runtimeEnv: {
|
||||
DATABASE_URL: process.env.DATABASE_URL,
|
||||
NODE_ENV: process.env.NODE_ENV,
|
||||
// NEXT_PUBLIC_CLIENTVAR: process.env.NEXT_PUBLIC_CLIENTVAR,
|
||||
},
|
||||
/**
|
||||
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially
|
||||
* useful for Docker builds.
|
||||
*/
|
||||
skipValidation: !!process.env.SKIP_ENV_VALIDATION,
|
||||
/**
|
||||
* Makes it so that empty strings are treated as undefined. `SOME_VAR: z.string()` and
|
||||
* `SOME_VAR=''` will throw an error.
|
||||
*/
|
||||
emptyStringAsUndefined: true,
|
||||
});
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
import { postRouter } from "~/server/api/routers/post";
|
||||
import { createCallerFactory, createTRPCRouter } from "~/server/api/trpc";
|
||||
|
||||
/**
|
||||
* This is the primary router for your server.
|
||||
*
|
||||
* All routers added in /api/routers should be manually added here.
|
||||
*/
|
||||
export const appRouter = createTRPCRouter({
|
||||
post: postRouter,
|
||||
});
|
||||
|
||||
// export type definition of API
|
||||
export type AppRouter = typeof appRouter;
|
||||
|
||||
/**
|
||||
* Create a server-side caller for the tRPC API.
|
||||
* @example
|
||||
* const trpc = createCaller(createContext);
|
||||
* const res = await trpc.post.all();
|
||||
* ^? Post[]
|
||||
*/
|
||||
export const createCaller = createCallerFactory(appRouter);
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
import { z } from "zod";
|
||||
|
||||
import { createTRPCRouter, publicProcedure } from "~/server/api/trpc";
|
||||
|
||||
export const postRouter = createTRPCRouter({
|
||||
hello: publicProcedure
|
||||
.input(z.object({ text: z.string() }))
|
||||
.query(({ input }) => {
|
||||
return {
|
||||
greeting: `Hello ${input.text}`,
|
||||
};
|
||||
}),
|
||||
|
||||
create: publicProcedure
|
||||
.input(z.object({ name: z.string().min(1) }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
return ctx.db.post.create({
|
||||
data: {
|
||||
name: input.name,
|
||||
},
|
||||
});
|
||||
}),
|
||||
|
||||
getLatest: publicProcedure.query(async ({ ctx }) => {
|
||||
const post = await ctx.db.post.findFirst({
|
||||
orderBy: { createdAt: "desc" },
|
||||
});
|
||||
|
||||
return post ?? null;
|
||||
}),
|
||||
});
|
||||
|
|
@ -1,106 +0,0 @@
|
|||
/**
|
||||
* YOU PROBABLY DON'T NEED TO EDIT THIS FILE, UNLESS:
|
||||
* 1. You want to modify request context (see Part 1).
|
||||
* 2. You want to create a new middleware or type of procedure (see Part 3).
|
||||
*
|
||||
* TL;DR - This is where all the tRPC server stuff is created and plugged in. The pieces you will
|
||||
* need to use are documented accordingly near the end.
|
||||
*/
|
||||
import { initTRPC } from "@trpc/server";
|
||||
import superjson from "superjson";
|
||||
import { ZodError } from "zod";
|
||||
|
||||
import { db } from "~/server/db";
|
||||
|
||||
/**
|
||||
* 1. CONTEXT
|
||||
*
|
||||
* This section defines the "contexts" that are available in the backend API.
|
||||
*
|
||||
* These allow you to access things when processing a request, like the database, the session, etc.
|
||||
*
|
||||
* This helper generates the "internals" for a tRPC context. The API handler and RSC clients each
|
||||
* wrap this and provides the required context.
|
||||
*
|
||||
* @see https://trpc.io/docs/server/context
|
||||
*/
|
||||
export const createTRPCContext = async (opts: { headers: Headers }) => {
|
||||
return {
|
||||
db,
|
||||
...opts,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 2. INITIALIZATION
|
||||
*
|
||||
* This is where the tRPC API is initialized, connecting the context and transformer. We also parse
|
||||
* ZodErrors so that you get typesafety on the frontend if your procedure fails due to validation
|
||||
* errors on the backend.
|
||||
*/
|
||||
const t = initTRPC.context<typeof createTRPCContext>().create({
|
||||
transformer: superjson,
|
||||
errorFormatter({ shape, error }) {
|
||||
return {
|
||||
...shape,
|
||||
data: {
|
||||
...shape.data,
|
||||
zodError:
|
||||
error.cause instanceof ZodError ? error.cause.flatten() : null,
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Create a server-side caller.
|
||||
*
|
||||
* @see https://trpc.io/docs/server/server-side-calls
|
||||
*/
|
||||
export const createCallerFactory = t.createCallerFactory;
|
||||
|
||||
/**
|
||||
* 3. ROUTER & PROCEDURE (THE IMPORTANT BIT)
|
||||
*
|
||||
* These are the pieces you use to build your tRPC API. You should import these a lot in the
|
||||
* "/src/server/api/routers" directory.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This is how you create new routers and sub-routers in your tRPC API.
|
||||
*
|
||||
* @see https://trpc.io/docs/router
|
||||
*/
|
||||
export const createTRPCRouter = t.router;
|
||||
|
||||
/**
|
||||
* Middleware for timing procedure execution and adding an artificial delay in development.
|
||||
*
|
||||
* You can remove this if you don't like it, but it can help catch unwanted waterfalls by simulating
|
||||
* network latency that would occur in production but not in local development.
|
||||
*/
|
||||
const timingMiddleware = t.middleware(async ({ next, path }) => {
|
||||
const start = Date.now();
|
||||
|
||||
if (t._config.isDev) {
|
||||
// artificial delay in dev
|
||||
const waitMs = Math.floor(Math.random() * 400) + 100;
|
||||
await new Promise((resolve) => setTimeout(resolve, waitMs));
|
||||
}
|
||||
|
||||
const result = await next();
|
||||
|
||||
const end = Date.now();
|
||||
console.log(`[TRPC] ${path} took ${end - start}ms to execute`);
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
/**
|
||||
* Public (unauthenticated) procedure
|
||||
*
|
||||
* This is the base piece you use to build new queries and mutations on your tRPC API. It does not
|
||||
* guarantee that a user querying is authorized, but you can still access user session data if they
|
||||
* are logged in.
|
||||
*/
|
||||
export const publicProcedure = t.procedure.use(timingMiddleware);
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
import { env } from "~/env";
|
||||
import { PrismaClient } from "../../generated/prisma";
|
||||
|
||||
const createPrismaClient = () =>
|
||||
new PrismaClient({
|
||||
log:
|
||||
env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"],
|
||||
});
|
||||
|
||||
const globalForPrisma = globalThis as unknown as {
|
||||
prisma: ReturnType<typeof createPrismaClient> | undefined;
|
||||
};
|
||||
|
||||
export const db = globalForPrisma.prisma ?? createPrismaClient();
|
||||
|
||||
if (env.NODE_ENV !== "production") globalForPrisma.prisma = db;
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
import {
|
||||
defaultShouldDehydrateQuery,
|
||||
QueryClient,
|
||||
} from "@tanstack/react-query";
|
||||
import SuperJSON from "superjson";
|
||||
|
||||
export const createQueryClient = () =>
|
||||
new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
// With SSR, we usually want to set some default staleTime
|
||||
// above 0 to avoid refetching immediately on the client
|
||||
staleTime: 30 * 1000,
|
||||
},
|
||||
dehydrate: {
|
||||
serializeData: SuperJSON.serialize,
|
||||
shouldDehydrateQuery: (query) =>
|
||||
defaultShouldDehydrateQuery(query) ||
|
||||
query.state.status === "pending",
|
||||
},
|
||||
hydrate: {
|
||||
deserializeData: SuperJSON.deserialize,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import { QueryClientProvider, type QueryClient } from "@tanstack/react-query";
|
||||
import { httpBatchStreamLink, loggerLink } from "@trpc/client";
|
||||
import { createTRPCReact } from "@trpc/react-query";
|
||||
import { type inferRouterInputs, type inferRouterOutputs } from "@trpc/server";
|
||||
import { useState } from "react";
|
||||
import SuperJSON from "superjson";
|
||||
|
||||
import { type AppRouter } from "~/server/api/root";
|
||||
import { createQueryClient } from "./query-client";
|
||||
|
||||
let clientQueryClientSingleton: QueryClient | undefined = undefined;
|
||||
const getQueryClient = () => {
|
||||
if (typeof window === "undefined") {
|
||||
// Server: always make a new query client
|
||||
return createQueryClient();
|
||||
}
|
||||
// Browser: use singleton pattern to keep the same query client
|
||||
clientQueryClientSingleton ??= createQueryClient();
|
||||
|
||||
return clientQueryClientSingleton;
|
||||
};
|
||||
|
||||
export const api = createTRPCReact<AppRouter>();
|
||||
|
||||
/**
|
||||
* Inference helper for inputs.
|
||||
*
|
||||
* @example type HelloInput = RouterInputs['example']['hello']
|
||||
*/
|
||||
export type RouterInputs = inferRouterInputs<AppRouter>;
|
||||
|
||||
/**
|
||||
* Inference helper for outputs.
|
||||
*
|
||||
* @example type HelloOutput = RouterOutputs['example']['hello']
|
||||
*/
|
||||
export type RouterOutputs = inferRouterOutputs<AppRouter>;
|
||||
|
||||
export function TRPCReactProvider(props: { children: React.ReactNode }) {
|
||||
const queryClient = getQueryClient();
|
||||
|
||||
const [trpcClient] = useState(() =>
|
||||
api.createClient({
|
||||
links: [
|
||||
loggerLink({
|
||||
enabled: (op) =>
|
||||
process.env.NODE_ENV === "development" ||
|
||||
(op.direction === "down" && op.result instanceof Error),
|
||||
}),
|
||||
httpBatchStreamLink({
|
||||
transformer: SuperJSON,
|
||||
url: getBaseUrl() + "/api/trpc",
|
||||
headers: () => {
|
||||
const headers = new Headers();
|
||||
headers.set("x-trpc-source", "nextjs-react");
|
||||
return headers;
|
||||
},
|
||||
}),
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<api.Provider client={trpcClient} queryClient={queryClient}>
|
||||
{props.children}
|
||||
</api.Provider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
}
|
||||
|
||||
function getBaseUrl() {
|
||||
if (typeof window !== "undefined") return window.location.origin;
|
||||
if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`;
|
||||
return `http://localhost:${process.env.PORT ?? 3000}`;
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
import "server-only";
|
||||
|
||||
import { createHydrationHelpers } from "@trpc/react-query/rsc";
|
||||
import { headers } from "next/headers";
|
||||
import { cache } from "react";
|
||||
|
||||
import { createCaller, type AppRouter } from "~/server/api/root";
|
||||
import { createTRPCContext } from "~/server/api/trpc";
|
||||
import { createQueryClient } from "./query-client";
|
||||
|
||||
/**
|
||||
* This wraps the `createTRPCContext` helper and provides the required context for the tRPC API when
|
||||
* handling a tRPC call from a React Server Component.
|
||||
*/
|
||||
const createContext = cache(async () => {
|
||||
const heads = new Headers(await headers());
|
||||
heads.set("x-trpc-source", "rsc");
|
||||
|
||||
return createTRPCContext({
|
||||
headers: heads,
|
||||
});
|
||||
});
|
||||
|
||||
const getQueryClient = cache(createQueryClient);
|
||||
const caller = createCaller(createContext);
|
||||
|
||||
export const { trpc: api, HydrateClient } = createHydrationHelpers<AppRouter>(
|
||||
caller,
|
||||
getQueryClient
|
||||
);
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
# Use this script to start a docker container for a local development database
|
||||
|
||||
# TO RUN ON WINDOWS:
|
||||
# 1. Install WSL (Windows Subsystem for Linux) - https://learn.microsoft.com/en-us/windows/wsl/install
|
||||
# 2. Install Docker Desktop or Podman Deskop
|
||||
# - Docker Desktop for Windows - https://docs.docker.com/docker-for-windows/install/
|
||||
# - Podman Desktop - https://podman.io/getting-started/installation
|
||||
# 3. Open WSL - `wsl`
|
||||
# 4. Run this script - `./start-database.sh`
|
||||
|
||||
# On Linux and macOS you can run this script directly - `./start-database.sh`
|
||||
|
||||
# import env variables from .env
|
||||
set -a
|
||||
source .env
|
||||
|
||||
DB_PASSWORD=$(echo "$DATABASE_URL" | awk -F':' '{print $3}' | awk -F'@' '{print $1}')
|
||||
DB_PORT=$(echo "$DATABASE_URL" | awk -F':' '{print $4}' | awk -F'\/' '{print $1}')
|
||||
DB_NAME=$(echo "$DATABASE_URL" | awk -F'/' '{print $4}')
|
||||
DB_CONTAINER_NAME="$DB_NAME-postgres"
|
||||
|
||||
if ! [ -x "$(command -v docker)" ] && ! [ -x "$(command -v podman)" ]; then
|
||||
echo -e "Docker or Podman is not installed. Please install docker or podman and try again.\nDocker install guide: https://docs.docker.com/engine/install/\nPodman install guide: https://podman.io/getting-started/installation"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# determine which docker command to use
|
||||
if [ -x "$(command -v docker)" ]; then
|
||||
DOCKER_CMD="docker"
|
||||
elif [ -x "$(command -v podman)" ]; then
|
||||
DOCKER_CMD="podman"
|
||||
fi
|
||||
|
||||
if ! $DOCKER_CMD info > /dev/null 2>&1; then
|
||||
echo "$DOCKER_CMD daemon is not running. Please start $DOCKER_CMD and try again."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if command -v nc >/dev/null 2>&1; then
|
||||
if nc -z localhost "$DB_PORT" 2>/dev/null; then
|
||||
echo "Port $DB_PORT is already in use."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "Warning: Unable to check if port $DB_PORT is already in use (netcat not installed)"
|
||||
read -p "Do you want to continue anyway? [y/N]: " -r REPLY
|
||||
if ! [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
echo "Aborting."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$($DOCKER_CMD ps -q -f name=$DB_CONTAINER_NAME)" ]; then
|
||||
echo "Database container '$DB_CONTAINER_NAME' already running"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$($DOCKER_CMD ps -q -a -f name=$DB_CONTAINER_NAME)" ]; then
|
||||
$DOCKER_CMD start "$DB_CONTAINER_NAME"
|
||||
echo "Existing database container '$DB_CONTAINER_NAME' started"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$DB_PASSWORD" = "password" ]; then
|
||||
echo "You are using the default database password"
|
||||
read -p "Should we generate a random password for you? [y/N]: " -r REPLY
|
||||
if ! [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
echo "Please change the default password in the .env file and try again"
|
||||
exit 1
|
||||
fi
|
||||
# Generate a random URL-safe password
|
||||
DB_PASSWORD=$(openssl rand -base64 12 | tr '+/' '-_')
|
||||
if [[ "$(uname)" == "Darwin" ]]; then
|
||||
# macOS requires an empty string to be passed with the `i` flag
|
||||
sed -i '' "s#:password@#:$DB_PASSWORD@#" .env
|
||||
else
|
||||
sed -i "s#:password@#:$DB_PASSWORD@#" .env
|
||||
fi
|
||||
fi
|
||||
|
||||
$DOCKER_CMD run -d \
|
||||
--name $DB_CONTAINER_NAME \
|
||||
-e POSTGRES_USER="postgres" \
|
||||
-e POSTGRES_PASSWORD="$DB_PASSWORD" \
|
||||
-e POSTGRES_DB="$DB_NAME" \
|
||||
-p "$DB_PORT":5432 \
|
||||
docker.io/postgres && echo "Database container '$DB_CONTAINER_NAME' was successfully created"
|
||||
|
|
@ -9,25 +9,32 @@
|
|||
"moduleDetection": "force",
|
||||
"isolatedModules": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
|
||||
/* Strictness */
|
||||
"strict": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"checkJs": true,
|
||||
|
||||
/* Bundled projects */
|
||||
"lib": ["dom", "dom.iterable", "ES2022"],
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"ES2022"
|
||||
],
|
||||
"noEmit": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"jsx": "preserve",
|
||||
"plugins": [{ "name": "next" }],
|
||||
"jsx": "react-jsx",
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"incremental": true,
|
||||
|
||||
/* Path Aliases */
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"~/*": ["./src/*"]
|
||||
"~/*": [
|
||||
"./src/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
|
|
@ -36,7 +43,12 @@
|
|||
"**/*.tsx",
|
||||
"**/*.cjs",
|
||||
"**/*.js",
|
||||
".next/types/**/*.ts"
|
||||
".next/types/**/*.ts",
|
||||
".next/dev/types/**/*.ts"
|
||||
],
|
||||
"exclude": ["node_modules", "generated", "cgol/pkg"]
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"generated",
|
||||
"cgol/pkg"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue