All Blog Posts (12)

Ola Mundo

table of contents I am be written to test md parser works or not Welcome to my page This is a sample markdown page that demonstrates various markdown features. Text Formatting You can write text in bold, italic, or both. You can also use ~~strikethrough~~ text. Lists Here's an unordered list: First item Second item Third item with a link Numbered list: 1. First step 2. Second step 3. Third step Code Example Here's an inline code example: `console.log('Hello World!')` And a code block: def greet(): print("Hello, World!") Quote > This is a blockquote. > This is another blockquote. Table | Header 1 | Header 2 | | -------- | -------- | | Cell 1 | Cell 2 | | Cell 3 | Cell 4 | I am an image example Image Example Will I be attacked? <script> alert('Hello World!'); </script> <iframe src="https://www.google.com"></iframe>

by John Doe
text
markdown
sample

Play Cloudflare

table of contents I use Cloudflare R2 to store my blog data it is similar to AWS S3, but instead of 5GB free storage for first 12 months, it has 10GB free storage, forever. Cloudflare R2 aws S3 Pricing considering my website will move to aws someday, and probably will use aws s3 for my blog data, but for now, I use Cloudflare R2, since even their sdk are compatible. Yes, I can use aws-sdk to operate r2. Cloudflare R2 with S3 API to make our life easier, here I share my r2 operation script. create client const R2Storage = new S3Client({ region: 'auto', endpoint: `https://${CLOUDFLARER2ACCOUNT_ID}.r2.cloudflarestorage.com`, credentials: { accessKeyId: CLOUDFLARER2ACCESSKEYID, secretAccessKey: CLOUDFLARER2SECRETACCESSKEY, }, }); Oh, I forgot to mention, if you want to use r2 by access key and access id, you have to create an API token with permission of Admin Read&Write even if you only want to access one bucket. > this is a aws sdk bug, because aws sdk tries to create a bucket if it doesn't exist, even though it is not allowed to do so. basic operations list buckets export const listBuckets = async () => { return await R2Storage.send(new ListBucketsCommand({})); }; list objects in bucket export const listObjectsInBucket = async ( bucketName: string ) => { return await R2Storage.send(new ListObjectsV2Command({ Bucket: bucketName })); }; get object const getObject = async ( key: string, bucketName: string ) => { return await R2Storage.send( new GetObjectCommand({ Bucket: bucketName, Key: key }) ); }; has object in bucket export const hasObjectInBucket = async ( key: string, bucketName: string ): Promise<boolean> => { try { await R2Storage.send( new GetObjectCommand({ Bucket: bucketName, Key: key }) ); return true; } catch (error) { return false; } }; delete object export const deleteObject = async ( key: string, bucketName: string ) => { await R2Storage.send( new DeleteObjectCommand({ Bucket: bucketName, Key: key, }) ); }; update object As we all know, bucket objects are immutable, so to update an object, we have to delete the old one and create a new one. export const updateObject = async ( filePath: string, key: string, checkIfExists: boolean = true, bucketName: string ) => { // I defined checkIfExists because sometimes we are very sure that an object is in bucket or not. const exists = checkIfExists ? await hasObjectInBucket(key, bucketName) : true; if (exists) { await deleteObject(key, bucketName); } return await uploadObject(filePath, key, bucketName); }; upload object export const uploadObject = async ( filePath: string, key: string = path.basename(filePath), bucketName: string ) => { //have to set content type, otherwise images can not be read const contentType = mime.lookup(filePath) || 'application/octet-stream'; return await R2Storage.send( new PutObjectCommand({ Bucket: bucketName, Key: key, Body: await fs.promises.readFile(filePath), ContentType: contentType, }) ); }; download object export const downloadObject = async ( key: string, intoDirName: string = 'public', bucketName: string ) => { const intoDir = process.cwd() + '/' + intoDirName; const object = await getObject(key, bucketName); // Create the full directory path including subdirectories const fullPath = path.join(intoDir, key); const targetDir = path.dirname(fullPath); await fs.promises.mkdir(targetDir, { recursive: true }); // Convert the readable stream to a buffer and write to file /eslint-disable @typescript-eslint/no-explicit-any/ const chunks: any[] = []; for await (const chunk of object.Body as any) { chunks.push(chunk); } /eslint-enable @typescript-eslint/no-explicit-any/ const buffer = Buffer.concat(chunks); await fs.promises.writeFile(fullPath, buffer); return fullPath; }; bucket does not have the concept of folder, I use folder instead of prefix to make it easier to understand. download folder export const downloadFolder = async ( prefix: string, intoDirName: string = 'public', bucketName: string ) => { const response = await R2Storage.send( new ListObjectsV2Command({ Bucket: bucketName, Prefix: prefix, }) ); if (!response.Contents) { return []; } // Download each object const downloads = response.Contents.map((object) => { if (!object.Key) return; return downloadObject(object.Key, intoDirName, bucketName); }).filter(Boolean); // Wait for all downloads to complete const downloadedPaths = await Promise.all(downloads); return downloadedPaths; }; upload folder export const uploadFolder = async ( folderPath: string, prefix: string = '', bucketName: string ) => { const files = await fs.promises.readdir(folderPath, { withFileTypes: true }); const uploads = []; for (const file of files) { const fullPath = path.join(folderPath, file.name); const key = path.join(prefix, file.name).replace(/\\/g, '/'); if (file.isDirectory()) { uploads.push(uploadFolder(fullPath, key, bucketName)); } else { uploads.push(uploadObject(fullPath, key, bucketName)); } } await Promise.all(uploads); return true; }; I want to sync my blog data to r2 every time I push to github, we talk about this later.

by Eddie Zhang
cloudflare
r2
snippets

Use Git Hooks

in the last blog, I mentioned to use git hooks to format my code before commit. surprisingly, it's not hard to do it. the native way to use git hooks is to edit the `.git/hooks` directory, but it's not recommended since .git folder is ignored by git, it will cause troubles when other guys commit. so i use husky, this is a git hook tool specially for js users. After getting started, you can see the husky is working. by the way husky doc mentions there is another useful tool called lint-staged, it will run the lint command for different file formats concurrently, but for my current project, i don't need it since I got over 99% code in typescript. Extra Q: I run my lint command as a part of build command, so why I need to use git hooks? A: because if eslint yells at build stage in your remote server, things become worse, am I right?

by Eddie Zhang
git
husky
git hooks

Solve Your End-of-line (eol) Problem For Good

I don't want to explain it too much, I understand why you come to this blog setup for your editor if you use vscode, create a `.vscode/settings.json` { "files.eol": "\n" } or a much general solution, create a `.editorconfig` file [*] endofline = lf in this way, even if an editor haven't set eol sequence to LF, this will do it for you for frontend developers in your `.prettierrc` file { "endOfLine": "lf" } you know what to do next but it still happens well, even though you set up these stuffs during developing, eol issue still happens when you clone a repository from a windows user. After your `git commit`, git saves your code in CRLF on windows, while LF on linux/macos. to handle this, create a `.gitattributes` file * text=auto eol=lf *.{cmd,[cC][mM][dD]} text eol=crlf *.{bat,[bB][aA][tT]} text eol=crlf this config tells git to use LF on saving any file, except .cmd or .bat not done yet run following commands to reset eol. Before it, you should make sure your local repository is clean (commit/stash) git ls-files --eol inspect if there are files use crlf git rm --cached -r . clear git index git reset --hard after these, eol in current files will be set to LF

by Eddie Zhang
eol

A Record Of Adding Eslint To My Nextjs Website

adding eslint support to nextjs is definitely not as easy as the official claims! what happened? nextjs has supported eslint by default, then I thought "maybe a just need a .eslintrc file, how hard can it be?", then one afternoon of my life is ruined. I copied my .eslintrc file from my old project, and I promised I exactly followed nextjs migrate instruction > tldr, nextjs prompts you to extend "next" in your own .eslintrc.json then nothing happened when I run "pnpm run lint", nextjs has no idea about my .eslintrc.json file. this popped up ⚠ The Next.js plugin was not detected in your ESLint configuration. See https://nextjs.org/docs/basic-features/eslint#migrating-existing-config After ~~copying~~ referring to many stackoverflow/github issues, I decided to screw that bullcrap next lint, I do it my self! these are necessary dependencies, notice they are all devDependencies "@typescript-eslint/eslint-plugin": "^8.18.2", "@typescript-eslint/parser": "^8.18.2",// to let eslint understand typescript "eslint": "^9.17.0", "eslint-config-next": "^15.1.3",// to avoid conflict with nextjs eslint config "eslint-config-prettier": "^9.1.0", //to avoid conflict with prettier config "prettier": "^3.4.2", then we will be okay...? after eslint 9, .eslintrc files (js or json or whatever) are not supported, which means I have to make a eslint.config.mjs instead to let eslint cli know my config file > is breaking changes ths mainstream in javascript? am I right, Nextjs official? Good news is, eslint official tells me how to migrate to the new version: pnpx @eslint/migrate-config .eslintrc.json these msgs will pop up Migrating .eslintrc.json Wrote new config to ./eslint.config.mjs You will need to install the following packages to use the new config: globals @eslint/js @eslint/eslintrc You can install them using the following command: npm install globals @eslint/js @eslint/eslintrc -D download... actually we good, pnpx @eslint/migrate-config .eslintrc.json && eslint --no-cache eslint shouts, in the end. setup prettier eslint shouts when our rules are broken, but prettier fix our code (kind of) to introduce some plugins I am using: "prettier-plugin-organize-imports": "^4.1.0",// this will conflict with eslint-import-sort, I recommend to use prettier "prettier-plugin-tailwindcss": "^0.6.9",// format tw classnames go to your .prettierrc, "plugins": [ "prettier-plugin-tailwindcss", "prettier-plugin-organize-imports" ], run `prettier write .` to format all the files in your project > why this command can run globally? modify my commands "lint": "pnpx @eslint/migrate-config .eslintrc.json && eslint --no-cache", "format": "pnpm run lint && prettier --write .", > I am not getting used to the latest eslint config syntax, you can make your own's don't forget to go to next.config.ts const nextConfig: NextConfig = { eslint: { ignoreDuringBuilds: true, }, //... }; since we run `format` every time before `build` so we don't need it anymore currently I am making it into a git hook, I will write a new blog after I made it

by Eddie Zhang
eslint
nextjs
prettier

The Correct Way To Download Ios Runtime

the general workflow is to download ios runtime in xcode, but xcode shouted download is failed > this is not only for me, lots of developers reported this issue, I think it's Apple's fault. then we have to download it manually. go to <https://developer.apple.com/download/all/?q=Xcode> to download ios runtime you like. > tips for Chinese developers, you need a 🪜 for normal access After your dmg file is downloaded, then go to your shell, tell which xcode to load you ios, mine is at default path sudo xcode-select -s /Applications/Xcode.app xcodebuild -runFirstLaunch xcrun simctl runtime add "~/Downloads/<somewhat.dmg>" the forgoing is what Apple doc told you, but what it hasn't told you is: Accessing '/Users/<Username>/Downloads/<somewhat.dmg>' requires Security & Privacy approval. since terminal cannot add files to disk by default, you should go to your mac settings, Security & Privacy > Full disk access > Give terminal full permission run those bash commands again, D: 134B74D0-63F2-43ED-9E2B-21B5739CA8A5 iOS (18.2 - 22C150) (Ready) if this pops up means you are okay Then you can play iphone 16 in your mac!

by Eddie Zhang
xcode
ios runtime

Pitfalls On Orbstack Proxy

how to set proxy to orbstack? Well, on my new computer I installed orbstack to ease my work, but when I was pulling a mirror, a familiar message shows up: Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers) don't need to say more, this is the classic proxy issue, let's add some proxies, it differs from docker/daemon.json, you should go here vim ~/.orbstack/config/docker.json add registries just as the same as docker { "registry-mirrors": [ "https://<some-proxy>.com" ] } open a new shell and pull nginx Well,it doesn't help, things go tricky orbstack documents says we can also set network proxies, let's have it a try orb config set network_proxy socks5://127.0.0.1:7890 this command sets proxy of terminal, I added it just in case export http_proxy=socks5://127.0.0.1:7890 see if it is set orb config show it should work, right? NO! then the final resort is to add dns ip addresses to your computer after add a bunch of dns records it works in the end.

by Eddie Zhang
orbstack
proxy

Quick Notes

just some quick notes in my coding, if there is enough content, I will create a new blog for it. fix the eol issue make a `.gitattributes` * text=auto eol=lf default LF *.{cmd,[cC][mM][dD]} text eol=crlf CRLF for cmd *.{bat,[bB][aA][tT]} text eol=crlf CRLF for bat replace all the files with LF in old files git rm --cached -r . git reset --hard fix the revalidate issue [❌] export const revalidate = 15*60; //error [✅] export const revalidate = 900; //success set git proxy git config --global http.proxy '<http|socks5>://<hostname>:<port>' cannot set https proxy because git doesn't have this option see the config is set or not git config --global --get http.proxy fix drizzle orm generate error pnpx drizzle-kit generate cli shows this error Please install latest version of drizzle-orm issue in short, pnpm pick wrong version of drizzle-orm even if we have the latest version in `package.json`. solution: npx drizzle-kit generate or pnpm exec drizzle-kit generate

by Eddie Zhang
quick notes

What Exactly Is "use Client" In Nextjs?

the 'use client' directive is a confusing content in Nextjs, because it actually differs from the conventional acknowledgement to client side rendering How? let's make an example, you will get what I mean. export const LOG = () => { if (typeof window !== undefined) { console.log('I am on client side'); } if (process.env.NODE_ENV) { console.log('I am on server side'); } }; let's use it to page component import { LOG } from '@/lib/LOG'; const Page = () => { LOG(); }; export default Page; the function logs both in my terminal and in my browser, it is quite reasonable because nextjs uses SSR let' add 'use client' notation to the page 'use client'; import { LOG } from '@/lib/LOG'; const Page = () => { LOG(); }; export default Page; the function still logs in both of my terminal and my browser, but it logs twice in the browser (due to react strict mode) See? 'use client' still uses SSR, hence actually the full name of 'use client' is actually 'use client feature', not 'use client rendering', that's a huge difference. So why nextjs invent it? See the doc. nextjs has two separate strategies to make it happen: 1. Full page load: nextjs renders a full page of static HTML on server side, then use js script to append browser features on client side. 2. Subsequent navigation: First, what is "subsequent navigation"? I believe nextjs document didn't explain it well. But it sounds like "client navigation" to me, probably it means the Link component in next/link. Anyway, in this case, nextjs directly sends js bundle to browser, this is the real "client side rendering". by marking 'use client' at the top of the tsx file, obviously nextjs uses the first strategy, because these components still use SSR, just like the example above. Then, it makes great sense to explains why nextjs mentions the unsupported pattern in using client components because initially all the components are rendered on server side, when hydration happens on client side, it means the render is done, then nextjs cannot go back to server side to handle server features. have to use the 'use server' directive to handle server features. Key points 1. client components are more than the components use 'use client' 2. the components use 'use client' are hydrated on client side 3. the components use 'use client' are rendered on server side (at first stage) 4. 'use server' can only be used in the components use 'use client' Extra somebody thinks maybe it causes hydration error because of the "use client" directive, but actually it is not. const Page = () => { return ( <p> <h3>This is index</h3> </p> ); }; This code causes hydration error, because the this is invalid html structure (p tags can only contain inline elements, h3 is block element)

by Eddie Zhang
Nextjs
use client

Write A React Switch Case Component

In React, we often find ourselves writing conditional rendering like: `{condition && <Component/>}`. Examples from this Website use-and-operator use-ternary-operator From my perspective, this pattern significantly impacts code maintainability and readability, yet such pattern has already became part of react syntax. > I have some thoughts about typescript as well, but we discuss that another time :) Is There a Better Way? During my time learning vuejs in university, I discovered its elegant solution to this problem via built-in directives. Not familiar? See the docs. Of course, Vue.js can implement directives easily because it is based on template. Adding directives to React would be more difficult because we are going to use babel plugins. So I will use slots, or react children, and I really make it in the end! Here's my implementation: react-switch-case > For the sake of shadcn/ui - I have no idea how many methods that the React instance has in last several years 😅 Rather than using If-else blocks, I prefer switch cases for better readability. And the jsx syntax returns values, hence we no longer need to write break statements. Here's a working example, just like the switch-case everyone knows 😎 react-switch-case-example Go to this link to experience demo

by Eddie Zhang
eureka moment

Useful Links For Developers

Some useful links for developers, to avoid terrible frontend codes. UI Does anyone not know about shadcn/ui? To be honest this guy saves everyone's life :). However, shadcn is too atomic, it's not easy to use it as a full-fledged UI. Shadcn The following ui libraries are based on shadcn, but are more like normal ui libraries for developers. Origin UI Magic UI Aceternity UI <-this website uses it, a lot other recommended ui libraries, i,e, not based on shadcn Next UI<- not related to nextjs Headless UI Icons Iconify Tabler <-I prefer this one Hero Icons Lucide Icons <-integrated with shadcn/ui React Icons <- once popular, but now I don't use it anymore Other annoying stuffs ico converter favicon generator looking for brand logos make logo React hooks I just recommend this one, because I don't like to write the same codes over and over again. react-use I guess these are the all the things for frontend developers. As for backend developers... ngrok

by Eddie Zhang
links

Dotnet Cli Cheatsheet

I prefer typing commands over clicking (blame VSCode for that). General Workflow List all templates provided by Microsoft dotnet new list <template name> --tag=<tag> For template names with spaces, please use double quotes Or search for a template on NuGet dotnet new search <template name> --tag=<tag> Create a new application using the template's short name dotnet new <template short name> > Unlike other CLI tools, the dotnet CLI creates files and folders directly in your current directory, so make sure to create a directory first before running this command. Run the application (navigate to the project folder first) dotnet run Build the application dotnet build .NET SDK Commands List all SDK versions installed on your machine, The command structure differs from the usual pattern dotnet --list-sdks Check current SDK version dotnet --version > You can download SDKs from the official .NET download page > > Alternatively, use the .NET SDK extension in VSCode as shown below: > dotnet-install-tool SDKs are installed globally, and the CLI automatically uses the latest version. How to Switch SDK Versions Navigate to your project root and run: dotnet new globaljson > This command works for both new and existing projects

by Eddie Zhang
snippets
dotnet