AI 资讯
The 4-test protocol that isolated a 9 ms Stripe SDK crash on Next 16
The number that lied Friday May 15, 4:13 PM. The Sentry alert pings on my phone. The first Phase 1 re-enrolling student waits in front of the payment screen, her name at the top of my tab. I put down the can, I reopen the screen. The mug with Françoise's face on it, on the desk next door, catches a yellow reflection I notice without looking at. The stack trace fills the screen. The stack trace opens, nine fields out of ten at null , and a number I didn't see coming. type = "StripeConnectionError" message = "An error occurred with our connection to Stripe." code = null statusCode = null requestId = null duration = 9 ms Nine milliseconds. On a Vercel route in Paris region, DNS resolves in forty ms, a TLS handshake costs one to two hundred. Nine milliseconds isn't a network call that failed. It's a network call that never happened. The SDK didn't reach the wire. Instinct immediately offers three patches. Vercel serverless timeout — I add maxDuration , redeploy. Revoked key — I'll rotate it. Stripe account restricted after the live switch — I open a support ticket. These three hypotheses are plausible. None of the three is falsifiable from the symptom alone, and that's precisely what makes them dangerous: each opens a fifteen-to-thirty-minute cycle with rollback at the end if it's wrong. Multiplied by three, half a day lost with the customer still clicking. I don't have time. A student is waiting. Four tests, in order I know the incident class — "preview works, prod breaks" , or its mirror. The rule for this class is that you fix nothing until you've discriminated the layers. Four tests, executed in order. Each eliminates a family of hypotheses, not an isolated hypothesis. And each is designed to refute what it interrogates — because a test that seeks to confirm always finds, by selection, what it's looking for. Test 1 — reproduce in the witness environment. I rerun the same funnel in preview, with the sk_test_ key. Checkout opens in three hundred fourteen milliseconds,
AI 资讯
How I built an automated SBOM scanner to secure my supply chain 🛡️
Supply chain security is terrifying right now. With new vulnerabilities popping up daily and governments mandating compliance (like the EU CRA and US Executive Orders), I realized my open-source projects were completely flying blind. I needed a Software Bill of Materials (SBOM) to track exactly what dependencies I was shipping. But every tool I found was either a massive enterprise platform or a clunky CLI tool that took forever to set up. So, I built my own. It's called Deptic . 🏗️ The Architecture I wanted the developer experience to be completely frictionless: you paste a GitHub URL, and it instantly spits out a compliant SBOM and highlights any critical CVEs. Here is the tech stack I went with: Next.js 14 (App Router): For a lightning-fast React frontend and seamless API routes. Go (Golang): The backend scanning engine. Go's incredible concurrency allows it to parse massive dependency trees in milliseconds. Supabase: For database management and instant authentication. Tailwind CSS: Because writing raw CSS is pain. 🧩 The Hardest Part: Dependency Resolution Building the UI was easy. Parsing package.json or go.mod files? Also easy. The hardest part was recursively walking down the dependency tree to find transitive dependencies (the dependencies of your dependencies). I had to write custom parsers that could speak to the NPM registry, PyPI, and Maven Central simultaneously to map out the entire tree and cross-reference them with global CVE databases in real-time. 🚀 The Result What started as a weekend script turned into a full platform. Deptic now supports: Instant scanning of public GitHub repos. Generating perfectly compliant CycloneDX (1.5) and SPDX (2.3) JSON files. Live CVE vulnerability detection. Try it out! If you want to see exactly what dependencies are hiding in your codebase, you can run a free scan here: 👉 deptic.netlify.app It's completely free for developers. I would love to get your brutal feedback on the UI, the scanning speed, or any feature reque
AI 资讯
Generating valid .ics calendar feeds at build time
A few weeks ago I shipped a feature I'd been putting off because it felt like it needed a backend: subscribable calendar feeds. "Add this holiday to Google Calendar." "Subscribe to all your country's public holidays so they show up in Apple Calendar forever." Every calendar competitor has this. My site had none. The catch: the whole thing is a static export — next build produces a folder of HTML/CSS/JS that I drop on Cloudflare Pages. No server, no API routes at request time, no ISR. So how do you serve a .ics feed that a calendar app polls every few hours? Turns out you don't need a server at all. Here's the approach, the RFC 5545 gotchas that bit me, and the parts I'd tell my past self. The "aha": a feed is just a file A .ics subscription feed is not a live API. It's a static text file that calendar clients re-fetch on a schedule. So for a static site, the idiomatic move is a post-build emitter : after next build , run a Node script that walks your data and writes assets straight into out/ . # scripts/deploy.sh npx next build node scripts/emit-feeds.mjs # writes .ics + .json into out/ That's the entire architecture. The emitter reads the same JSON the pages render from, so the feeds can never drift out of sync with the site — there's one source of truth. It emits: a per-year feed ( holidays-de-2026.ics ) a per-holiday feed (one event, for the "download this day" button) an all-years subscription feed (the one you point webcal:// at) and, almost for free in the same loop, a JSON API under out/api/ No new pages, no new routes. Just files. RFC 5545: all-day events are sneakier than they look I assumed an all-day event on Jan 1 would be DTSTART:20260101 , DTEND:20260101 . Wrong. DTEND is exclusive. A one-day all-day event ends on Jan 2 : BEGIN:VEVENT UID:de-2026-neujahr@calendana.com DTSTAMP:20260614T101500Z DTSTART;VALUE=DATE:20260101 DTEND;VALUE=DATE:20260102 SUMMARY:Neujahr TRANSP:TRANSPARENT CATEGORIES:Holiday END:VEVENT Get this wrong and some clients render a ze
AI 资讯
I Cut My Next.js + Supabase App Load Time by 73% - Here Are the 5 Techniques That Actually Worked
I Cut My Next.js + Supabase App Load Time by 73% - Here Are the 5 Techniques That Actually Worked Last month, our SaaS dashboard was embarrassingly slow . 4.2 seconds to load the main page. Users were complaining. Conversion rates were tanking. Today? 1.1 seconds . 73% faster. Here's exactly what worked (and what didn't). The Problem: Death by a Thousand Database Calls Our dashboard showed user projects, team members, recent activity, and notifications. Sounds simple, right? Wrong. Each component was making its own database calls. The projects list fetched projects, then made separate calls for each project's stats. The activity feed loaded events, then fetched user details for each event. Classic N+1 query problem, but worse. Technique #1: Strategic Data Fetching Consolidation Before: 47 database calls to load the dashboard After: 3 database calls The fix wasn't fancy. We consolidated related data into single queries using Supabase's nested select syntax: // ❌ Before: Multiple separate calls const projects = await supabase . from ( ' projects ' ). select ( ' * ' ) const stats = await Promise . all ( projects . map ( p => supabase . from ( ' project_stats ' ). select ( ' * ' ). eq ( ' project_id ' , p . id )) ) // ✅ After: Single consolidated call const projects = await supabase . from ( ' projects ' ) . select ( ` *, project_stats(*), team_members(count), recent_activity:activities(*, user:users(name, avatar_url)) ` ) . limit ( 10 ) Result: Dashboard load time dropped from 4.2s to 2.8s (33% improvement) Technique #2: Aggressive Caching with Smart Invalidation Most dashboard data doesn't change every second. We implemented a three-tier caching strategy: // Static data: Cache indefinitely const categories = await supabase . from ( ' categories ' ) . select ( ' * ' ) . cache ({ revalidate : false }) // Semi-static data: Cache with revalidation const userProjects = await supabase . from ( ' projects ' ) . select ( ' * ' ) . eq ( ' user_id ' , userId ) . cache ({ revali
AI 资讯
Next.js + Supabase Performance Optimization: From Slow to Lightning Fast
Next.js + Supabase Performance Optimization: From Slow to Lightning Fast Last month, I optimized a Next.js + Supabase application that was frustratingly slow. Initial page load took 4.2 seconds, Lighthouse performance score was 62, and users were complaining. After applying these optimization techniques, we achieved: 70% faster load times (4.2s → 1.3s) Lighthouse score of 96 (up from 62) LCP improved by 65% (3.8s → 1.3s) 50% reduction in database queries Here's exactly how we did it. The Starting Point: Measuring Performance Before optimizing anything, we measured current performance using: Lighthouse (Chrome DevTools): Performance: 62 First Contentful Paint (FCP): 2.1s Largest Contentful Paint (LCP): 3.8s Total Blocking Time (TBT): 420ms Cumulative Layout Shift (CLS): 0.18 Real User Monitoring: Average page load: 4.2s Time to Interactive: 5.1s Database query time: 850ms average The Problems: Unoptimized database queries No caching strategy Large JavaScript bundles Unoptimized images Blocking render paths Too many client-side fetches Let's fix each one. 1. Database Query Optimization Problem: N+1 Queries The biggest performance killer was N+1 queries. We were fetching posts, then fetching the author for each post individually. // ❌ Bad: N+1 queries (1 + N database calls) async function getPosts () { const { data : posts } = await supabase . from ( ' posts ' ) . select ( ' id, title, author_id ' ) // Fetching author for each post = N queries const postsWithAuthors = await Promise . all ( posts . map ( async ( post ) => { const { data : author } = await supabase . from ( ' users ' ) . select ( ' name, avatar ' ) . eq ( ' id ' , post . author_id ) . single () return { ... post , author } }) ) return postsWithAuthors } Impact: 50 posts = 51 database queries (850ms total) Solution: Use Joins // ✅ Good: Single query with join (1 database call) async function getPosts () { const { data : posts } = await supabase . from ( ' posts ' ) . select ( ` id, title, author:users(nam
AI 资讯
7 Things I Wish I Knew Before Scaling Next.js + Supabase to 100K Users
7 Things I Wish I Knew Before Scaling Next.js + Supabase to 100K Users Six months ago, we launched our SaaS with Next.js and Supabase. The stack was perfect for our MVP: fast development, great DX, and it just worked. Then we hit 10K users. Then 50K. Then 100K. Everything that worked beautifully at small scale started breaking. Database queries that took 50ms now took 5 seconds. Our Supabase bill went from $25/month to $800/month. Users complained about slow page loads. Here's what I wish someone had told me before we started. 1. RLS Policies Are Not Optional (Even in Development) We skipped RLS in development. "We'll add it before launch," we said. Launch day came. We enabled RLS on all tables. The app broke in 47 different places. Queries that worked suddenly returned empty arrays. Inserts failed with permission errors. We spent 12 hours fixing RLS policies while users waited. What I'd do differently: Enable RLS from day one. Write policies as you create tables: CREATE TABLE posts ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4 (), title TEXT NOT NULL , user_id UUID REFERENCES auth . users ( id ) ); -- Enable RLS immediately ALTER TABLE posts ENABLE ROW LEVEL SECURITY ; -- Write policies now, not later CREATE POLICY "Users can view own posts" ON posts FOR SELECT USING ( auth . uid () = user_id ); Test with RLS enabled. If it works in development, it'll work in production. 2. Database Indexes Are Not Premature Optimization "We'll add indexes when we need them." We needed them on day 3. Our posts feed query went from 50ms to 8 seconds as we hit 10K posts. Users complained. We scrambled to add indexes during peak traffic. The query: const { data } = await supabase . from ( ' posts ' ) . select ( ' *, profiles(*) ' ) . eq ( ' published ' , true ) . order ( ' created_at ' , { ascending : false }) . limit ( 20 ) The fix: CREATE INDEX posts_published_created_at_idx ON posts ( published , created_at DESC ) WHERE published = true ; Query time dropped to 12ms. What I'd do di
AI 资讯
How We Built a Zero-Upload PDF Editor in WebAssembly to Beat the $108/yr Paywalls
For years, whenever I needed to merge two PDFs or compress a file to upload to a government portal, I would Google "compress PDF", click the first result, and inevitably hit a paywall. "You have reached your 2 free files per day limit." Worse, I was uploading sensitive documents—tax returns, medical records, and NDAs—to random servers in God-knows-where just to strip out some heavy images. I decided to build an alternative. I wanted it to be 100% free, have absolutely no daily limits, and most importantly: zero server uploads . Here is how we built PDF Pro using Next.js and WebAssembly to process PDFs entirely natively inside the user's browser. The Architecture: Why WebAssembly? Traditional PDF tools (like Smallpdf or iLovePDF) use a monolithic server architecture. You upload your file to their AWS bucket, their backend runs a Python or C++ script (usually using Ghostscript or a proprietary library) to manipulate the PDF, and then you download the processed file. This architecture is expensive (high bandwidth and compute costs) and creates a massive privacy liability. By compiling a C++ PDF manipulation library down to WebAssembly (WASM) , we inverted the architecture. 1. The Build Process We took pdf-lib and custom C++ compression algorithms and compiled them to a lightweight .wasm binary. When a user visits PDF Pro Compress , their browser downloads the ~2MB WASM file once and caches it. 2. Client-Side Processing When you drag and drop a 50MB PDF into the UI, it never hits our server. Instead, the browser's JavaScript engine passes a Pointer to the file data directly into the WebAssembly memory buffer. The WASM module executes native C++ speeds directly on your local CPU to compress or merge the document. Performance Benchmarks Because there is zero upload and zero download time, the performance metrics are staggering: 10MB PDF Compression (Cloud): ~15 seconds (Upload) + 4 seconds (Process) + 5 seconds (Download) = 24 seconds . 10MB PDF Compression (PDF Pro WASM)
AI 资讯
I automated my Gumroad product screenshots with Playwright
I automated my Gumroad product screenshots with Playwright I recently started packaging a few small frontend projects as digital products, and one surprisingly annoying part was preparing product screenshots. Manual screenshots quickly became messy: different browser sizes inconsistent cropping blurry images mobile screenshots were easy to get wrong Gumroad needed a square thumbnail every update meant taking screenshots again So I built a small local screenshot workflow with Next.js and Playwright. The workflow captures: desktop screenshots mobile screenshots square thumbnail images consistent PNG outputs route status checks basic console error reporting basic horizontal overflow checks The basic command flow is: npm run build npm run start npm run screenshots The script reads a simple config file, opens the configured local routes, captures each screenshot with consistent viewport settings, and exports the images into a predictable folder. For example: screenshots/gumroad/ landing.png dashboard.png template-preview.png mobile-preview.png thumbnail.png I found this especially useful when preparing Gumroad product galleries, because I could regenerate all product images after every UI change instead of taking screenshots manually. This is not a hosted screenshot service. It is just a local source-code workflow for people who want to generate product screenshots from their own Next.js pages. I packaged the workflow as a small Gumroad product here: https://remix410.gumroad.com/l/screenshot-automation-kit Curious how other developers handle product screenshots. Do you take them manually, use Playwright/Puppeteer, or use a design tool workflow?
AI 资讯
Inngest + Next.js: The Complete Guide (2026)
Serverless functions have a time limit. Vercel gives you 60 seconds on Pro. None of that is enough to send a welcome email sequence, process an uploaded CSV, or run any workflow with multiple external API calls. Inngest solves this by turning your Next.js API routes into reliable, retryable, observable background jobs — without Redis, without a worker process, without any separate queue infrastructure. Read the full guide with all code examples at stacknotice.com How It Works You define functions that run in response to events . When an event fires, Inngest calls your function via HTTP. If the function fails, Inngest retries it. If the function has multiple steps, Inngest checkpoints each step so a failure halfway through doesn't restart from scratch. Setup npm install inngest // lib/inngest/client.ts import { Inngest } from ' inngest ' export const inngest = new Inngest ({ id : ' my-app ' }) // app/api/inngest/route.ts import { serve } from ' inngest/next ' import { inngest } from ' @/lib/inngest/client ' import { welcomeSequence } from ' @/lib/inngest/functions/welcome ' export const { GET , POST , PUT } = serve ({ client : inngest , functions : [ welcomeSequence ], }) Welcome Email Sequence export const welcomeSequence = inngest . createFunction ( { id : ' welcome-email-sequence ' , retries : 3 }, { event : ' user/signed-up ' }, async ({ event , step }) => { const { userId , email , name } = event . data // Each step is independently retried await step . run ( ' send-welcome-email ' , async () => { await resend . emails . send ({ from : ' hello@yourapp.com ' , to : email , subject : `Welcome, ${ name } !` , html : `<p>Thanks for signing up...</p>` , }) }) await step . sleep ( ' wait-2-days ' , ' 2 days ' ) await step . run ( ' send-tips-email ' , async () => { await resend . emails . send ({ from : ' hello@yourapp.com ' , to : email , subject : ' Top 5 tips to get the most out of the app ' , html : `<p>Here are the tips...</p>` , }) }) await step . sleep ( ' wait
AI 资讯
Upstash Redis + Next.js: The Complete Guide (2026)
Redis is fast. But self-hosting Redis on a serverless stack is a nightmare — cold starts, connection pool exhaustion, and managing a persistent server that your serverless functions keep hammering. Upstash solves this with an HTTP-based Redis API that scales to zero, charges per request, and works natively with Next.js App Router. This guide covers the patterns that actually matter in production: cache-aside with proper TTLs, SWR (stale-while-revalidate), session storage, and pub/sub. Real code, real trade-offs. Read the full article with all code examples at stacknotice.com Why Upstash Over a Traditional Redis Instance Standard Redis uses persistent TCP connections. Serverless functions don't maintain persistent connections — every invocation potentially opens a new one. At scale, you hit ECONNREFUSED or max connection errors that are annoying to debug and expensive to fix. Upstash's @upstash/redis client talks over HTTP/REST. No connection pool, no connection limit headaches. Each request is stateless. This is exactly what Next.js Server Components and Route Handlers need. Other advantages: Pay per request — a cache that never gets hit costs $0 Global replication — low latency from any Vercel edge region Native Edge Runtime support — works in Next.js middleware Free tier — 10,000 commands/day, no credit card needed Setup npm install @upstash/redis // lib/redis.ts import { Redis } from ' @upstash/redis ' export const redis = new Redis ({ url : process . env . UPSTASH_REDIS_REST_URL ! , token : process . env . UPSTASH_REDIS_REST_TOKEN ! , }) Pattern 1: Cache-Aside // lib/cache.ts import { redis } from ' ./redis ' export async function withCache < T > ( key : string , fetcher : () => Promise < T > , options : { ttl ?: number ; prefix ?: string } = {} ): Promise < T > { const { ttl = 300 , prefix = ' cache ' } = options const cacheKey = ` ${ prefix } : ${ key } ` const cached = await redis . get < T > ( cacheKey ) if ( cached !== null ) return cached const data = awai
AI 资讯
Building GeoPrizm: Turning Global News Events into a Bilateral Relations Index
I recently built GeoPrizm , a free and open-source dashboard for tracking bilateral relations through global news event signals. The idea is simple: instead of reading dozens of headlines every day and trying to guess whether a relationship is improving or worsening, can we turn public news event data into a readable trend signal? GeoPrizm is my attempt at that. Website: https://www.geoprizm.com/en GitHub: https://github.com/Haullk/relationship-temperature The problem International relations are usually discussed through headlines, speeches, official statements, and expert commentary. That is valuable, but it creates a few practical problems: It is hard to compare country pairs on the same scale. A single headline can feel more important than it really is. Readers often see conclusions before they see the underlying signals. Most non-specialists do not have time to follow every event in detail. I wanted a lightweight way to answer one question: Based on public news event signals, is this bilateral relationship trending more cooperative, neutral, or tense? Data source: GDELT GeoPrizm uses the GDELT global news event database. GDELT monitors global news coverage and converts news reports into structured event records. These records include fields such as: actor countries event date CAMEO event category GoldsteinScale value number of mentions number of articles source information For GeoPrizm, the key idea is to focus on events where two countries appear as actors, then aggregate the cooperation or conflict signals over time. From event signals to an index Each bilateral pair is converted into a 0-100 relationship index. The midpoint is 50. Above 50 means the recent signal is more cooperative or favorable. Around 50 means the signal is relatively neutral or mixed. Below 50 means the recent signal is more tense or conflict-heavy. The rough process is: Select recent GDELT events for a country pair. Keep events where both actors are present and the GoldsteinScale value is
开发者
I Thought My Next.js 16 Auth Was Solid. One Afternoon Proved Otherwise.
A few months ago I was doing a final pre-release review on a client project before handing it...
开发者
From Blank Terminal to Shipping a Real Client Project: My First Year of Coding
Exactly one year ago, my terminal was a blank slate. I started where almost everyone does , wrestling with HTML, CSS, and JavaScript, trying to understand the web pixel by pixel. What began as curiosity quickly turned into a full obsession. I went from building simple static pages to diving deep into full-stack development. Here’s what that intense first year of constant building, breaking things, and shipping real projects has looked like. The Leap into Modern Frameworks Once vanilla JavaScript started feeling limiting, I jumped into React . Component-based thinking completely changed how I approached interfaces. Then came Next.js — it bridged client-side beauty with server-side power and pushed me into a true full-stack mindset. The Ultimate Test: Lynvista Safaris The biggest challenge was building lynvistasafaris.com , a live travel booking platform for a real client. This project forced me far beyond tutorials and into real engineering problems. I had to implement: Payment infrastructure from scratch with Daraja API (M-Pesa STK pushes) and Paystack for international transactions. A robust database layer using Drizzle ORM + MySQL. Custom business logic , including a multi-currency pricing system with special validation rules for currencies like GBP. It was messy , many late nights debugging webhooks, schema mismatches, and edge cases , but shipping it taught me more than any course ever could. What One Year Taught Me Syntax is just a tool. The real skill is learning how to break down complex problems, debug effectively, and keep iterating even when things break in production. I’m incredibly proud of the progress I’ve made in 365 days (402 contributions later), but I know I’m still at the very beginning. For the next phase, I’m focused on shipping more projects, writing about the crazy bugs I encounter, and deepening my architectural and full-stack skills. If you want to see what I’m building right now, check out my GitHub .
AI 资讯
I built an AI that reads stock charts — and made it grade its own homework
How KlineVision does AI technical analysis with Next.js + Gemini, draws it on the real candles, and verifies every call it makes after the fact — misses included. Most "AI stock analysis" tools confidently tell you a chart looks bullish and then never look back. I wanted the opposite: a tool that records every call it makes and later checks whether the market actually agreed — and publishes the hit rate, misses included. That one constraint — keep yourself honest — ended up shaping most of the interesting engineering. Here's how KlineVision is put together. What it does Type a ticker ( AAPL , 600519.SH , 0700.HK ) — or drop a chart screenshot. You get a structured read : trend, support/resistance, candlestick + chart patterns, and 缠论 (Chan theory) structure — drawn directly onto the real candles , not hand-waved in prose. Works across US, A-shares, and Hong Kong markets. The stack Next.js (App Router) on Vercel Supabase (Postgres + RLS) for data & auth Gemini 3 Flash for the analysis itself EODHD + Yahoo Finance for market data GitHub Actions + Vercel Cron for the background jobs PostHog for product analytics Now the parts that were actually interesting to build. 1. "Never analyze fake data" The market-data layer had a silent fallback: when a provider rate-limited, it returned synthetic candles so the UI never broke. Great for a demo, terrible for a product whose entire value is trust — the model would write a beautiful, confident analysis of a chart that never existed . The fix was to make the data layer fail honestly : Yahoo (primary) → EODHD (fallback) → if only demo data is available → 503, no analysis If we can't get real candles, the user gets "live market data temporarily unavailable" — not a hallucinated read. For a trust tool, a silent fallback to fake data is the worst bug on the board. 2. Make the AI show its work A text blob saying "there's a double bottom" isn't credible. You have to see it on the chart. So the model returns structured overlays keyed to
AI 资讯
Build a Realtime Translation App with Gemini Live API, LiveKit, & Google Cloud Run
Imagine speaking in English, and having listeners from all over the world hear you translated into...
AI 资讯
How I Built a Free SEO Audit Tool with Next.js, Supabase, and Stripe in 1 Week
Last weekend I started building SiteGrade — a free instant SEO audit tool that gives any website an A–F letter grade and the top fixes ranked by impact. I just launched it. This post is the build log: the architecture decisions that worked, the ones that didn't, and the gotchas you'd save time knowing about. If you're considering building a similar audit/scanner tool, or just want to see what a 2026 Next.js + Supabase + Stripe stack looks like in practice, this should be useful. Why I built it I got tired of family and friends asking me "how's the SEO on my website?" and not having a good answer to send back. Every existing tool I tried either cost $99+/month (Ahrefs, Semrush) or threw 200 metrics at people who just wanted to know if their site was OK. The wedge I built around: one letter grade, three top fixes, plain English, no signup. The paid tier ($29/mo) re-audits weekly and emails the report so non-technical users can track improvements as their developer makes them. The stack Nothing exotic. Everything is the obvious choice for a 2026 indie SaaS, which is the whole point — boring stack means I spent zero time fighting infrastructure and 100% of my time on the actual audit logic. Next.js 14 App Router — frontend + API routes in one repo Supabase — Postgres + auth + RLS + file storage, EU-hosted for GDPR Stripe — subscriptions + Billing Portal + webhooks Resend — transactional email (audit reports, weekly reports, Supabase auth via Custom SMTP) Vercel — hosting + cron jobs for the weekly re-audit cheerio — HTML parsing for the audit checks Google PageSpeed Insights API — Core Web Vitals + mobile performance Total monthly infrastructure cost at zero traffic: ~$0 (everyone on free tiers). At 100 paying customers it'd still be under $30/mo. The audit logic — what 15 checks look like in code Each audit runs 15 checks and produces a score from 0-100, then maps that to a letter grade (A: 90+, B: 75+, C: 60+, D: 45+, F: below 45). The checks are structured as pure fu
AI 资讯
I built a freelance rate calculator with Next.js
Here's the maths most calculators get wrong Most freelance rate calculators are wrong. Not buggy — conceptually wrong. They take your income goal, divide by hours, and hand you a number that will quietly lose you money all year. I got tired of explaining the correct calculation to freelancer friends, so I built a tool that does it properly. Here's the logic behind it, and a bit about how I built it. The flawed formula The typical calculator does this: hourly_rate = annual_income_goal / annual_hours_worked So if you want $80,000 and work 2,080 hours a year (40 × 52), it tells you to charge ~$38/hr. This is wrong in three separate ways. Fix 1: Tax is not optional Your income goal is a net number — what you want to keep. But you're taxed on gross. So the first correction is grossing up: const grossIncome = netTarget / ( 1 - taxRate ); // $80,000 / (1 - 0.28) = $111,111 That's already a $31,000 gap the naive formula ignores. Fix 2: You don't bill every hour you work This is the big one. Freelancers bill roughly 55–65% of their working hours. The rest is admin, proposals, invoicing, sales calls, and learning. const totalHours = weeksWorked * hoursPerWeek ; // 46 * 40 = 1840 const billableHours = totalHours * billableRatio ; // 1840 * 0.6 = 1104 So your real billable capacity isn't 2,080 hours. It's closer to 1,104. Pricing on the wrong denominator is how people end up working 50-hour weeks and still missing their income target. Fix 3: Business expenses come out of gross Software, hardware, insurance, courses — all real costs that need covering before you pay yourself: const totalNeeded = grossIncome + annualExpenses ; The corrected formula Putting it together: function minimumRate ({ netTarget , taxRate , expenses , weeks , hoursPerWeek , billableRatio }) { const gross = netTarget / ( 1 - taxRate ); const totalNeeded = gross + expenses ; const billableHours = weeks * hoursPerWeek * billableRatio ; return totalNeeded / billableHours ; } minimumRate ({ netTarget : 80000 ,
开发者
How I built a calculator site with 13 languages and 5,500 static pages in Next.js 15
A few months back I got frustrated. I needed to calculate something — nothing crazy, just a loan payment — and every site I landed on either wanted me to sign up, showed ads on every input, or was in English only (not helpful when sharing with family abroad). So I built Calculora . It's a free calculator site. 150+ tools, 13 languages, zero data collection. Everything runs in your browser — nothing gets sent anywhere. Why 13 languages? Because most people on the internet don't speak English as a first language. I wanted it to actually work for them — not just have a translated homepage, but real localized URLs, right-to-left layout for Arabic, the whole thing. Getting that right in Next.js took more time than I expected. Each tool has a different slug per language, so the routing has to map hundreds of combinations correctly. Totally worth it though. The math rendering For tools like the equation solver and statistics calculator, I wanted the formulas to look like real math — not just text. I used KaTeX for this. It's fast, lightweight, and renders LaTeX properly in the browser. The step-by-step solutions actually show the working — discriminant, roots, the full process — rendered cleanly. Some tools I'm proud of Equation Solver — linear and quadratic, with full steps shown Statistics Calculator — mean, median, mode, IQR, std dev, exportable FIRE Calculator — retirement planning done simply Debt Snowball/Avalanche — side-by-side payoff comparison What's next More tools, better mobile experience, and filling in gaps across the language versions. If you try it, I'd genuinely love feedback — especially on anything that feels off or missing. calculora.net
AI 资讯
I built one self-hosted boilerplate and now I ship everything on it
Every time I started a side project, I rebuilt the same five things before I wrote a single line of the actual idea: auth, a database, file uploads, a deploy pipeline, and TLS. Different domain, same plumbing. By the third project I was copy-pasting my own docker-compose.yml from a folder two repos over and renaming things until they stopped erroring. So I stopped. I froze that plumbing into one boilerplate — PocketBase + Next.js + Caddy on a single cheap VPS — and now I ship every side project on it. Same shape every time: clone, rename, write the part that's actually new, push. The thing I care about most isn't the speed, though. It's that I stopped paying for it. A handful of real projects now run on this setup for a few euros a month, total — not a stack of per-service SaaS bills that each want $25 here and $20 there before you've shipped anything. No Vercel seat, no managed Postgres, no Auth-as-a-Service, no object-storage line item. Here's the whole thing. Why this stack The trick that makes the cost collapse is PocketBase . It's a single Go binary that gives you, in one process: Auth — email/password, OAuth, the works, with a real users collection A database — SQLite, with a schema you manage from an admin UI Realtime — subscribe to collection changes over SSE File storage — uploads handled, with on-the-fly thumbnails An admin dashboard — at /_/ , for free That's four or five separate SaaS products collapsed into one binary that runs anywhere and stores everything in a folder. Compared to wiring up Supabase or Firebase, the mental model is tiny: it's one process and one data directory. Back up the directory and you've backed up the entire app — database, uploaded files, auth tokens, all of it. For the front I use Next.js (App Router, React Server Components) because that's where I'm fastest, and Caddy as the reverse proxy because it gets you automatic HTTPS with zero config — it provisions and renews Let's Encrypt certificates on its own. And it all lives on
AI 资讯
From Native WordPress to Headless: The Real Engineering Decisions Behind a Production Migration
Every headless WordPress conversation starts the same way — someone draws an architecture diagram with arrows pointing from a REST API to a shiny Next.js frontend, and it looks clean. Too clean. This is a post about what happens when you close the whiteboard and open the actual codebase. The Stack Decision: GraphQL vs. REST vs. Direct MySQL This is usually the first fork in the road. For this build, the client already had a well-indexed WooCommerce site. The product catalog, slugs, and taxonomy structure were already doing heavy SEO work. So the constraint was simple: nothing about the data layer changes, only how we consume it. WPGraphQL was a real option — but it meant adding a plugin dependency to a WordPress install we were actively trying to slim down. The WP REST API was already there, no installation required, and exposed exactly what we needed: products, categories, pages, and media — all queryable by slug. The decision: WP REST API, consumed server-side via Next.js fetch in Server Components. // Fetching a product by slug — preserving the existing URL structure const res = await fetch ( ` ${ process . env . WP_API_BASE } /wp/v2/product?slug= ${ params . slug } &_embed` , { next : { revalidate : 3600 } } ); const [ product ] = await res . json (); No new dependencies on the WordPress side. The legacy install runs as a lean shell — no active theme, minimal plugins, just the REST API and the data. The Site Kit Problem: Bridging Familiar Workflows This is where most migrations quietly fail the client. The previous team lived inside WordPress admin. Google Site Kit gave them traffic stats, Search Console data, and Analytics — all surfaced in a UI they knew. Ripping that away and telling them "just use Google Analytics directly" is a workflow regression, not an upgrade. The pivot here was building a lightweight admin dashboard as part of the Next.js project — not a full replacement for Site Kit, but a mirror of the metrics they actually checked daily: Page views