feat: no more vercel, next. now vite and nix

This commit is contained in:
Jet Pham 2026-03-04 22:00:53 -08:00
parent fc80ead81e
commit bf5900edbb
No known key found for this signature in database
28 changed files with 4099 additions and 1554 deletions

View file

@ -1,14 +0,0 @@
# Since the ".env" file is gitignored, you can use the ".env.example" file to
# build a new ".env" file when you clone the repo. Keep this file up-to-date
# when you add new variables to `.env`.
# This file will be committed to version control, so make sure not to have any
# secrets in it. If you are cloning this repo, create a copy of this file named
# ".env" and populate it with your secrets.
# When adding additional environment variables, the schema in "/src/env.js"
# should be updated accordingly.
# Prisma
# https://www.prisma.io/docs/reference/database-reference/connection-urls#env
DATABASE_URL="postgresql://postgres:password@localhost:5432/website"

25
.gitignore vendored
View file

@ -1,25 +1,11 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies # dependencies
/node_modules /node_modules
/.pnp
.pnp.js
# testing # testing
/coverage /coverage
# database # vite
/prisma/db.sqlite /dist
/prisma/db.sqlite-journal
db.sqlite
# next.js
/.next/
/out/
next-env.d.ts
# production
/build
# misc # misc
.DS_Store .DS_Store
@ -31,14 +17,10 @@ yarn-debug.log*
yarn-error.log* yarn-error.log*
.pnpm-debug.log* .pnpm-debug.log*
# local env files # env files
# do not commit any .env files to git, except for the .env.example file. https://create.t3.gg/en/usage/env-variables#using-environment-variables
.env .env
.env*.local .env*.local
# vercel
.vercel
# typescript # typescript
*.tsbuildinfo *.tsbuildinfo
@ -46,4 +28,3 @@ yarn-error.log*
.idea .idea
/.direnv /.direnv
/generated

1220
bun.lock

File diff suppressed because it is too large Load diff

View file

@ -1,13 +1,8 @@
import { FlatCompat } from "@eslint/eslintrc";
import tseslint from 'typescript-eslint'; import tseslint from 'typescript-eslint';
const compat = new FlatCompat({
baseDirectory: import.meta.dirname,
});
export default tseslint.config( export default tseslint.config(
{ {
ignores: ['.next', 'cgol/pkg/**/*', 'next-env.d.ts'] ignores: ['dist', 'cgol/pkg/**/*']
}, },
{ {
files: ['**/*.ts', '**/*.tsx'], files: ['**/*.ts', '**/*.tsx'],

12
flake.lock generated
View file

@ -20,11 +20,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1771369470, "lastModified": 1772542754,
"narHash": "sha256-0NBlEBKkN3lufyvFegY4TYv5mCNHbi5OmBDrzihbBMQ=", "narHash": "sha256-WGV2hy+VIeQsYXpsLjdr4GvHv5eECMISX1zKLTedhdg=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "0182a361324364ae3f436a63005877674cf45efb", "rev": "8c809a146a140c5c8806f13399592dbcb1bb5dc4",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -62,11 +62,11 @@
"nixpkgs": "nixpkgs_2" "nixpkgs": "nixpkgs_2"
}, },
"locked": { "locked": {
"lastModified": 1771384185, "lastModified": 1772679930,
"narHash": "sha256-KvmjUeA7uODwzbcQoN/B8DCZIbhT/Q/uErF1BBMcYnw=", "narHash": "sha256-FxYmdacqrdDVeE9QqZKTIpNLjv2B8GSKssgwlZuTR98=",
"owner": "oxalica", "owner": "oxalica",
"repo": "rust-overlay", "repo": "rust-overlay",
"rev": "23dd7fa91602a68bd04847ac41bc10af1e6e2fd2", "rev": "9b741db17141331fdb26270a1b66b81be8be9edd",
"type": "github" "type": "github"
}, },
"original": { "original": {

View file

@ -1,63 +1,44 @@
{ {
description = "CTF Jet development environment (Bun)"; description = "Jet Pham's personal website";
inputs = { inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
rust-overlay.url = "github:oxalica/rust-overlay"; rust-overlay.url = "github:oxalica/rust-overlay";
flake-utils.url = "github:numtide/flake-utils"; flake-utils.url = "github:numtide/flake-utils";
}; };
outputs = { self, nixpkgs, rust-overlay, flake-utils }: outputs = { self, nixpkgs, rust-overlay, flake-utils }:
flake-utils.lib.eachDefaultSystem (system: (flake-utils.lib.eachDefaultSystem (system:
let let
overlays = [ (import rust-overlay) ]; overlays = [ (import rust-overlay) ];
pkgs = import nixpkgs { pkgs = import nixpkgs { inherit system overlays; };
inherit system overlays; rustToolchain = pkgs.rust-bin.selectLatestNightlyWith (toolchain:
}; toolchain.default.override {
bun = pkgs.bun;
# Prisma engines for NixOS
prismaEngines = pkgs.prisma-engines;
devTools = with pkgs; [
git
postgresql
curl
wget
typescript-language-server
pkg-config
wasm-pack
binaryen
(rust-bin.selectLatestNightlyWith (toolchain: toolchain.default.override {
extensions = [ "rust-src" ]; extensions = [ "rust-src" ];
targets = [ "wasm32-unknown-unknown" ]; targets = [ "wasm32-unknown-unknown" ];
})) });
]; website = pkgs.stdenv.mkDerivation {
pname = "jet-website";
version = "0.1.0";
src = pkgs.lib.cleanSource ./.;
nativeBuildInputs = [ pkgs.nodejs rustToolchain pkgs.wasm-pack pkgs.binaryen pkgs.pkg-config ];
buildPhase = ''
export HOME=$TMPDIR
cd cgol && wasm-pack build --release --target web && cd ..
npm ci
npm run build
'';
installPhase = ''
mkdir -p $out
cp -r dist/* $out/
'';
};
in { in {
packages = { default = website; };
devShells.default = pkgs.mkShell { devShells.default = pkgs.mkShell {
buildInputs = [ buildInputs = with pkgs; [
bun nodejs git curl typescript-language-server
prismaEngines pkg-config wasm-pack binaryen rustToolchain
] ++ devTools; ];
NIXPKGS_ALLOW_UNFREE = "1";
PRISMA_QUERY_ENGINE_BINARY = "${prismaEngines}/bin/query-engine";
PRISMA_SCHEMA_ENGINE_BINARY = "${prismaEngines}/bin/schema-engine";
PRISMA_INTROSPECTION_ENGINE_BINARY = "${prismaEngines}/bin/introspection-engine";
PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING = "1";
};
packages = {
inherit bun prismaEngines;
default = pkgs.symlinkJoin {
name = "ctfjet-dev-bun";
paths = [ bun prismaEngines ];
};
}; };
} }
); ));
} }

18
index.html Normal file
View file

@ -0,0 +1,18 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#000000" />
<meta name="apple-mobile-web-app-title" content="Jet Pham" />
<title>Jet Pham - Software Extremist</title>
<meta name="description" content="Jet Pham's personal website" />
<link rel="manifest" href="/manifest.json" />
<link rel="icon" href="/favicon.ico" />
<link rel="apple-touch-icon" href="/apple-icon.png" />
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View file

@ -1,37 +0,0 @@
#!/bin/bash
set -euo pipefail
# fix home issues
export HOME=/root
# Install Rustup
if ! command -v rustup
then
echo "Installing Rustup..."
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y -t wasm32-unknown-unknown --profile minimal
source "$HOME/.cargo/env"
else
echo "Rustup already installed."
rustup target add wasm32-unknown-unknown
fi
# Install wasm-pack
if ! command -v wasm-pack
then
echo "Installing wasm-pack..."
curl https://drager.github.io/wasm-pack/installer/init.sh -sSf | sh
echo "wasm-pack installation complete."
else
echo "wasm-pack already installed."
fi
# Build cgol WASM package
echo "Building cgol WASM package..."
cd cgol
wasm-pack build --release --target web
cd ..
# Install Next.js dependencies with bun
echo "Installing Next.js dependencies with bun..."
bun install

View file

@ -1,100 +0,0 @@
/**
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially useful
* for Docker builds.
*/
/** @type {import("next").NextConfig} */
const config = {
webpack: (config, { isServer }) => {
config.module.rules.push({
test: /\.txt$/,
type: "asset/source",
});
config.experiments = {
...config.experiments,
asyncWebAssembly: true,
};
config.module.rules.push({
test: /\.wasm$/,
type: "asset/resource",
generator: {
filename: "static/wasm/[name].[hash][ext]",
},
});
// Ensure WASM files are properly handled
config.resolve.fallback = {
...config.resolve.fallback,
fs: false,
};
// Ensure WASM files are properly served
config.output.webassemblyModuleFilename = "static/wasm/[modulehash].wasm";
return config;
},
turbopack: {
rules: {
"*.txt": {
loaders: ["raw-loader"],
as: "*.js",
},
},
},
productionBrowserSourceMaps: false,
// Redirect /_not-found to /
async redirects() {
return [
{
source: '/_not-found',
destination: '/',
permanent: false,
},
];
},
// Ensure static files are properly served
async headers() {
return [
{
source: '/:path*',
headers: [
{
key: 'Cross-Origin-Opener-Policy',
value: 'same-origin',
},
{
key: 'Cross-Origin-Resource-Policy',
value: 'cross-origin',
},
],
},
{
source: '/_next/static/:path*',
headers: [
{
key: 'Cross-Origin-Resource-Policy',
value: 'cross-origin',
},
],
},
{
source: '/_next/static/wasm/:path*',
headers: [
{
key: 'Cross-Origin-Resource-Policy',
value: 'cross-origin',
},
{
key: 'Content-Type',
value: 'application/wasm',
},
],
},
];
},
};
export default config;

3970
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -4,52 +4,40 @@
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
"build": "next build", "build": "vite build",
"build:wasm": "cd cgol && wasm-pack build --release --target web", "build:wasm": "cd cgol && wasm-pack build --release --target web",
"check": "bun run lint && tsc --noEmit", "check": "npm run lint && tsc --noEmit",
"dev": "next dev --turbo", "dev": "vite",
"format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,mdx}\" --cache", "format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,mdx}\" --cache",
"format:write": "prettier --write \"**/*.{ts,tsx,js,jsx,mdx}\" --cache", "format:write": "prettier --write \"**/*.{ts,tsx,js,jsx,mdx}\" --cache",
"lint": "eslint .", "lint": "eslint .",
"lint:fix": "next lint --fix", "lint:fix": "eslint . --fix",
"preview": "next build && next start", "preview": "vite preview",
"start": "next start",
"typecheck": "tsc --noEmit" "typecheck": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@tanstack/react-query": "^5.90.21",
"anser": "^2.3.5", "anser": "^2.3.5",
"cgol": "file:./cgol/pkg", "cgol": "file:./cgol/pkg",
"escape-carriage": "^1.3.1", "escape-carriage": "^1.3.1",
"next": "^16.1.6",
"react": "^19.2.4", "react": "^19.2.4",
"react-dom": "^19.2.4", "react-dom": "^19.2.4"
"server-only": "^0.0.1",
"superjson": "^2.2.6"
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3.3.3", "@tailwindcss/vite": "^4.2.1",
"@next/eslint-plugin-next": "^16.1.6", "@types/node": "^25.3.3",
"@tailwindcss/postcss": "^4.2.0",
"@types/node": "^25.3.0",
"@types/react": "^19.2.14", "@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
"eslint": "^9", "@vitejs/plugin-react": "^5.1.4",
"eslint-config-next": "^16.1.6", "eslint": "^10",
"postcss": "^8.5.6",
"prettier": "^3.8.1", "prettier": "^3.8.1",
"prettier-plugin-tailwindcss": "^0.7.2", "prettier-plugin-tailwindcss": "^0.7.2",
"raw-loader": "^4.0.2", "tailwindcss": "^4.2.1",
"react-doctor": "^0.0.21",
"tailwindcss": "^4.2.0",
"typescript": "^5.9.3", "typescript": "^5.9.3",
"typescript-eslint": "^8.56.0", "typescript-eslint": "^8.56.1",
"webpack": "^5.105.2" "vite": "^7.3.1",
"vite-plugin-top-level-await": "^1.6.0",
"vite-plugin-wasm": "^3.5.0"
}, },
"ct3aMetadata": {
"initVersion": "7.40.0"
},
"overrides": {},
"knip": { "knip": {
"ignore": [ "ignore": [
"cgol/pkg/**" "cgol/pkg/**"

View file

@ -1,5 +0,0 @@
export default {
plugins: {
"@tailwindcss/postcss": {},
},
};

View file

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 979 KiB

After

Width:  |  Height:  |  Size: 979 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Before After
Before After

View file

@ -1,17 +1,10 @@
import Link from "next/link"; import { BorderedBox } from "~/components/bordered-box";
import Image from "next/image"; import { FrostedBox } from "~/components/frosted-box";
import { BorderedBox } from "./_components/bordered-box"; import Header from "~/components/header";
import { FrostedBox } from "./_components/frosted-box"; import { CgolCanvas } from "~/components/cgol-canvas";
import Header from "./_components/header"; import Jet from "~/assets/Jet.txt?raw";
import { CgolCanvas } from "./_components/cgol-canvas";
import FirstName from "~/assets/Jet.txt";
export const metadata = { export default function App() {
title: "Jet Pham - Software Extremist",
description: "Personal website of Jet Pham, a software extremist.",
};
export default async function Home() {
return ( return (
<> <>
<CgolCanvas /> <CgolCanvas />
@ -20,58 +13,57 @@ export default async function Home() {
<FrostedBox className="my-[2ch] w-full max-w-[66.666667%] min-w-fit md:mt-[4ch]"> <FrostedBox className="my-[2ch] w-full max-w-[66.666667%] min-w-fit md:mt-[4ch]">
<div className="flex flex-col items-center justify-center gap-[2ch] md:flex-row"> <div className="flex flex-col items-center justify-center gap-[2ch] md:flex-row">
<div className="order-1 flex flex-col items-center md:order-2"> <div className="order-1 flex flex-col items-center md:order-2">
<Header content={FirstName} /> <Header content={Jet} />
<div className="mt-[2ch]">Software Extremist</div> <div className="mt-[2ch]">Software Extremist</div>
</div> </div>
<div className="order-2 shrink-0 md:order-1"> <div className="order-2 shrink-0 md:order-1">
<Image <img
src="/jet.svg" src="/jet.svg"
alt="Jet" alt="Jet"
width={250} width={250}
height={250} height={250}
className="aspect-square w-full max-w-[250px] object-cover md:h-[263px] md:w-[175px] md:max-w-none" className="aspect-square w-full max-w-[250px] object-cover md:h-[263px] md:w-[175px] md:max-w-none"
priority
/> />
</div> </div>
</div> </div>
<BorderedBox label="Contact" className="mt-[2ch]"> <BorderedBox label="Contact" className="mt-[2ch]">
<Link href="mailto:jet@extremist.software"> <a href="mailto:jet@extremist.software">
jet@extremist.software jet@extremist.software
</Link> </a>
</BorderedBox> </BorderedBox>
<BorderedBox label="Links"> <BorderedBox label="Links">
<ol> <ol>
<li> <li>
<Link <a
href="https://git.extremist.software" href="https://git.extremist.software"
className="inline-flex items-center" className="inline-flex items-center"
> >
Forgejo Forgejo
</Link> </a>
</li> </li>
<li> <li>
<Link <a
href="https://github.com/jetpham" href="https://github.com/jetpham"
className="inline-flex items-center" className="inline-flex items-center"
> >
GitHub GitHub
</Link> </a>
</li> </li>
<li> <li>
<Link <a
href="https://x.com/jetpham5" href="https://x.com/jetpham5"
className="inline-flex items-center" className="inline-flex items-center"
> >
X X
</Link> </a>
</li> </li>
<li> <li>
<Link <a
href="https://bsky.app/profile/jetpham.com" href="https://bsky.app/profile/jetpham.com"
className="inline-flex items-center" className="inline-flex items-center"
> >
Bluesky Bluesky
</Link> </a>
</li> </li>
</ol> </ol>
</BorderedBox> </BorderedBox>

View file

@ -1,29 +0,0 @@
import "~/styles/globals.css";
import { type Metadata, type Viewport } from "next";
export const metadata: Metadata = {
title: "Jet Pham",
description: "Jet Pham's personal website",
appleWebApp: {
title: "Jet Pham",
},
};
export const viewport: Viewport = {
width: "device-width",
initialScale: 1,
themeColor: "#000000",
};
export default function RootLayout({
children,
}: Readonly<{ children: React.ReactNode }>) {
return (
<html lang="en">
<body>
{children}
</body>
</html>
);
}

View file

@ -1,5 +1,3 @@
"use client";
import { useEffect, useRef, useCallback } from "react"; import { useEffect, useRef, useCallback } from "react";
export function CgolCanvas() { export function CgolCanvas() {

7
src/global.d.ts vendored
View file

@ -1,10 +1,11 @@
declare module "*.txt" { /// <reference types="vite/client" />
declare module "*.txt?raw" {
const content: string; const content: string;
export default content; export default content;
} }
declare module "*.utf8ans" { declare module "*.utf8ans?raw" {
const content: string; const content: string;
export default content; export default content;
} }

10
src/main.tsx Normal file
View file

@ -0,0 +1,10 @@
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import "~/styles/globals.css";
import App from "./App";
createRoot(document.getElementById("root")!).render(
<StrictMode>
<App />
</StrictMode>,
);

View file

@ -23,11 +23,6 @@
"module": "ESNext", "module": "ESNext",
"moduleResolution": "Bundler", "moduleResolution": "Bundler",
"jsx": "react-jsx", "jsx": "react-jsx",
"plugins": [
{
"name": "next"
}
],
"incremental": true, "incremental": true,
/* Path Aliases */ /* Path Aliases */
"baseUrl": ".", "baseUrl": ".",
@ -38,17 +33,15 @@
} }
}, },
"include": [ "include": [
"next-env.d.ts",
"**/*.ts", "**/*.ts",
"**/*.tsx", "**/*.tsx",
"**/*.cjs", "**/*.cjs",
"**/*.js", "**/*.js",
".next/types/**/*.ts", "vite.config.ts"
".next/dev/types/**/*.ts"
], ],
"exclude": [ "exclude": [
"node_modules", "node_modules",
"generated", "dist",
"cgol/pkg" "cgol/pkg"
] ]
} }

23
vite.config.ts Normal file
View file

@ -0,0 +1,23 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
import wasm from "vite-plugin-wasm";
import topLevelAwait from "vite-plugin-top-level-await";
export default defineConfig({
plugins: [react(), tailwindcss(), wasm(), topLevelAwait()],
resolve: {
alias: {
"~": "/src",
},
},
server: {
headers: {
"Cross-Origin-Opener-Policy": "same-origin",
"Cross-Origin-Embedder-Policy": "require-corp",
},
},
build: {
target: "esnext",
},
});