"I already use Google Analytics"
GA tells you:
✗ Time on PAGE (includes idle tabs)
✗ Bounce rate (meaningless without context)
✗ Nothing about individual sections
✗ Nothing about which button they clicked
PageTracker Pro tells you:
✓ Time on each SECTION
✓ Real active time (idle paused)
✓ Every button click with label + count
✓ Exactly where visitors drop off
Same site. Way more insight. No monthly bill.
One-time $19 → 🔗 https://t.co/EL0xflljcb
#GoogleAnalytics #WordPress #WebAnalytics #WPPlugin #SEO #DigitalMarketing #WordPressTips #CRO #IndieHacker #NoCode
Laravel queues work perfectly in development.
Then production happens.
A payment job runs twice. A customer is charged twice. Nobody knows why.
A job silently disappears because the failed_jobs table doesn't exist. An email was never sent. Nobody knows.
A worker crashes after three days because a job loaded 50,000 Eloquent models into memory. Queue depth climbs to 3,000. Nobody notices until a user complains.
None of these are rare edge cases. They're the default behaviour of a queue configuration that nobody touched after php artisan make:job.
Here's what's actually happening 👇
⚠️ The visibility timeout trap — why jobs run twice
config/queue.php:
'retry_after' => 90, // the default
This means: if a job runs longer than 90 seconds, Redis assumes the worker crashed and gives the job to another worker — while the first worker is still running.
Two workers. Same job. Same data. Running simultaneously.
If your job generates a PDF, sends an email, or calls an external API — and it takes over 90 seconds — you have this bug.
Fix: set retry_after higher than your longest job.
Set worker --timeout lower than retry_after.
The worker kills the job before Redis reassigns it.
💀 The failed_jobs table that doesn't exist
Default Laravel setup: failed_jobs table not created.
Result: jobs fail, exception is swallowed, job disappears.
You have no record it ever ran.
php artisan queue:failed-table
php artisan migrate
Two commands. Every failed job now has a full stack trace, the exception message, and the job payload — so you can retry it or understand what went wrong.
💥 QUEUE_CONNECTION=sync in production
Every new Laravel app defaults to sync. Every job blocks the HTTP response. There is no queue. There are no workers.
Before any app touches production:
QUEUE_CONNECTION=redis
💾 The memory leak nobody notices until it crashes
$products = Product::all(); // 50,000 rows into memory
foreach ($products as $p) { ... }
The worker runs fine. For three days. Then it crashes.
Fix: Product::chunk(200, ...) or Product::lazy()->each(...)
Peak memory: 200 products. Always.
Also: --max-jobs=500 on every worker.
Fresh process every 500 jobs. No memory accumulation possible.
🔧 The Supervisor config nobody writes correctly
stopwaitsecs must be > worker --timeout.
If Supervisor kills the worker before it finishes the current job:
Job is interrupted mid-execution. Runs again on next attempt. See: visibility timeout bug.
📖 Full guide — visibility timeout explained, failed jobs setup, queue prioritisation, idempotent job patterns, Horizon configuration, batch job failure handling, Supervisor config that actually works, and the 3am incident debugging checklist.
#Laravel #PHP #Queues #Redis #LaravelHorizon #BackendDevelopment #PHPDevelopment #WebDevelopment #SoftwareEngineering #LaravelPHP #BackendEngineering #DevOps #Programming #TechCommunity #SoftwareDevelopment #CodeQuality #SoftwareCraft #OpenSource #LaravelTips #ProductionReady https://t.co/dAWdLG83bK
Multi-tenancy is the architecture that makes SaaS possible.
It's also the architecture that causes the most catastrophic data leaks when implemented incorrectly.
One wrong query. Missing WHERE tenant_id = ?. Every customer's data exposed to every other customer.
Here's the complete picture — three architectures, Spatie's package, and the patterns that keep data where it belongs 👇
🏗️ The architecture decision you can't reverse
Single database: all tenants share tables with a tenant_id column.
✓ Cheapest. Simplest. Easy cross-tenant analytics.
✗ Missing tenant_id scope = catastrophic data leak. One noisy tenant hurts everyone.
Multiple databases: each tenant gets their own database.
✓ Complete isolation. GDPR delete = drop database. Performance isolation.
✗ Expensive. Migrations run against every database. Complex connection pooling.
Schema-per-tenant (PostgreSQL only): one database, isolated schemas.
✓ Better isolation than shared tables without full database cost.
✗ PostgreSQL only. Migration complexity similar to multi-DB.
The rule: if you need compliance (GDPR, HIPAA, SOC2) or enterprise clients → multiple databases. Otherwise → single database with tenant_id scoping.
⚡ spatie/laravel-multitenancy v4 — the bare essentials done right
The package's philosophy: provide only the bare essentials for multi-tenancy. Tenant finding, tenant switching, and a task system for what happens when a tenant becomes current. Your business logic stays in your code.
Tenant identification:
DomainTenantFinder resolves the current tenant from the request domain.
https://t.co/SIt3ZRgErQ → Tenant A
https://t.co/2rnFKb3dWV → Tenant B
Tenant switching tasks (run when a tenant becomes current):
→ SwitchTenantDatabaseTask — points the 'tenant' DB connection at their database
→ PrefixCacheTask — prefixes all cache keys with tenant ID (prevents cross-tenant cache reads)
→ Custom SwitchTenantStorageTask — isolates file uploads to per-tenant directories
📦 Queue tenant awareness — the detail everyone misses
With queues_are_tenant_aware_by_default = true:
Jobs dispatched during a tenant request automatically carry the tenant ID.
When the queue worker picks them up, the tenant context is restored.
No code changes in your jobs.
For global jobs (billing, reporting): use the NotTenantAware trait to opt out.
For running a task across all tenants:
Tenant::all()->eachCurrent(function (Tenant $tenant) {
// switches context for each tenant
Mail::to($users)->send(new MonthlyDigest());
});
🔄 The migration strategy that doesn't take your app down
Running migrations against 500 tenant databases synchronously during deployment = deployment timeout + hours of downtime.
The safe approach:
1. Landlord migrations run immediately (blocking, fast)
2. Tenant migrations queue as background jobs (staggered, non-blocking)
3. All tenant migrations add nullable columns or have defaults
4. Failures monitored via queue failure logging
📖 Full guide — all three architectures with honest trade-offs, complete Spatie v4 implementation, custom tenant finder for subdomain + custom domain, global scope security, storage and cache isolation, safe migration strategy, onboarding provisioning flow, and the production checklist.
#Laravel #PHP #MultiTenancy #SaaS #BackendDevelopment #PHPDevelopment #WebDevelopment #SoftwareEngineering #LaravelPHP #BackendEngineering #SoftwareArchitecture #Programming #TechCommunity #SoftwareDevelopment #CodeQuality #SoftwareCraft #OpenSource #DatabaseDesign #LaravelTips #Scalability https://t.co/PjLLTvpG1p
You're paying 13KB for axios.
fetch() in 2026 does most of what you're using it for. Here's what you missed 👇
━━━━━━━━━━━━━━━━━━━━━━━━
The old reason to leave: no error on 4xx/5xx
The fix: if (!response.ok) throw new ApiError(...)
One line. Encode it in a wrapper. Done.
━━━━━━━━━━━━━━━━━━━━━━━━
1️⃣ AbortSignal.timeout() — timeouts in one line
fetch('/api', { signal: AbortSignal.timeout(5000) })
No setTimeout. No controller.abort(). No cleanup.
TimeoutError ≠ AbortError — you know which one fired.
2️⃣ AbortSignal.any() — combine conditions
fetch('/api', {
signal: AbortSignal.any([
userController.signal,
AbortSignal.timeout(30_000)
])
})
First one to fire wins. Manual cancel + timeout in one signal.
3️⃣ Priority hints — tell the browser what matters
fetch('/api/hero', { priority: 'high' })
fetch('/api/recommendations', { priority: 'low' })
Browser schedules high-priority requests first.
Ignored by browsers that don't support it — safe to use today.
4️⃣ Response streaming — native
const reader = response.body.getReader()
while (true) {
const { done, value } = await https://t.co/9x6lNVQpfM()
if (done) break
processChunk(value) // arrives as it streams
}
No library. No buffering the full response. Works with AI token streams.
5️⃣ fetchLater() — the analytics API we always needed
Queus a request. Browser sends it when the tab closes.
Unlike sendBeacon: you can UPDATE the payload before it fires.
Call fetchLater() again with newer data —
the browser sends the most recent version on session end.
Final scroll depth. Last click. Actual session duration.
6️⃣ Response cloning — read a response twice
const clone = response.clone()
const data = await response.json() // consume original
await cache.put(url, clone) // cache the clone
The honest comparison:
fetch() wins: 0KB, streaming, fetchLater(), priority hints
Axios wins: upload progress (onUploadProgress is genuinely simpler)
Full guide — 50-line TypeScript wrapper, all patterns with complete code, feature availability table, and the honest comparison 👇
Are you still on axios? What's keeping you there?
#JavaScript #TypeScript #WebDevelopment #FrontendDevelopment #WebPerformance #FrontendEngineering #WebPlatform #Programming #SoftwareDevelopment #TechCommunity #WebDev #BundleOptimization #OpenSource #APIDesign #CodeQuality #SoftwareCraft #100DaysOfCode #NodeJS #DevExperience #VanillaJS https://t.co/IFe3Lv4her
Plain v-for on 10,000 rows:
1,840ms render. 15fps scroll. 312MB memory.
With the right stack:
38ms render. 60fps scroll. 48MB memory.
Here's exactly what changes 👇
━━━━━━━━━━━━━━━━━━━━━━━━
The problem: 10,000 items = ~50,000 DOM nodes
The solution: never render more than ~30
━━━━━━━━━━━━━━━━━━━━━━━━
1️⃣ Object.freeze() — free, instant win
const rows = ref(Object.freeze(data))
Vue skips deep reactivity. Memory drops. Every update is faster.
2️⃣ shallowRef for the array
Tracks the reference, not the contents.
Replace the array → triggers re-render.
Mutate individual items → doesn't (use triggerRef if needed).
3️⃣ v-memo — the directive most Vue devs haven't used
<tr v-memo="[row.updatedAt, selectedId === https://t.co/2TNeiV8wMX]">
When the array matches the previous render: zero work. No diff. No update.
Selection changes: only 2 rows re-render, not 10,000.
4️⃣ RecycleScroller (vue-virtual-scroller)
<RecycleScroller :items="rows" :item-size="48" key-field="id">
Renders only visible rows + buffer. ~30 DOM nodes always.
Requires: fixed container height + item-size matching actual height.
5️⃣ Web Worker for sorting and filtering
420ms sort blocking the main thread → 52ms non-blocking.
User can scroll and interact while data processes in the background.
6️⃣ Debounced filter + computed
Filter input debounced 300ms.
Computed only re-runs when the debounced value changes.
For 50,000+ rows: move filter to the Web Worker too.
The benchmark:
Approach | Render | Scroll | Memory
Plain v-for | 1,840ms | 15fps | 312MB
+ Object.freeze | 1,650ms | 18fps | 198MB
+ v-memo | 1,620ms | 45fps | 196MB
+ RecycleScroller| 38ms | 60fps | 48MB
+ Web Worker sort| 38ms | 60fps | 48MB (non-blocking)
Full guide — all 7 strategies with complete code, DynamicScroller for variable height, infinite scroll pattern, Tanstack Virtual as the headless alternative, and the performance checklist 👇
How many rows does your heaviest list render?
#VueJS #Vue3 #WebPerformance #FrontendDevelopment #JavaScript #TypeScript #VueMastery #FrontendEngineering #WebDevelopment #Programming #SoftwareDevelopment #TechCommunity #WebDev #UIEngineering #CoreWebVitals #PerformanceOptimization #OpenSource #DevExperience #100DaysOfCode #CodeQuality https://t.co/tleF80OJhu
Every application that handles documents faces the same moment.
"We need to extract text from this PDF."
Then comes the familiar chain: Which API? What's the per-page cost at scale? What happens to the data? Can we send a medical record or financial contract to a third-party server?
Parsel answers all of that at once.
It's an open-source PHP document parser that processes files locally on your server. No API keys. No data leaving your infrastructure. No per-page billing.
The API:
$text = Parsel::file('invoice.pdf')->text();
One line. That's it.
What it supports:
→ PDFs
→ Word documents (.docx)
→ Excel spreadsheets (.xlsx)
→ PowerPoint presentations (.pptx)
→ Images with OCR (Tesseract, multilingual)
→ Raw bytes (for uploaded files, database blobs)
What it returns:
→ Plain text — the full document as a string
→ Structured data — every text item with x, y, width, height, font name, font size, and OCR confidence
→ Page screenshots — render pages as images
→ Streamed pages — one page at a time for large documents
The structured output is what makes it genuinely powerful:
$document = Parsel::file('invoice.pdf')->parse();
foreach ($document->pages as $page) {
foreach ($page->items as $item) {
echo "{$item->text} @ ({$item->x}, {$item->y})";
echo " font: {$item->fontName} {$item->fontSize}pt";
echo " confidence: {$item->confidence}";
}
}
Coordinates + font metadata = document intelligence. Extract specific fields from invoices. Validate form layouts. Detect table structures. Build things cloud APIs can't give you at this resolution.
For large documents: lazyPages() processes one page at a time. Memory stays flat regardless of document size.
For testing: Parsel::fake() provides a fake runner. Tests work in CI without the binary installed.
For privacy: your users' contracts, medical records, and financial statements never leave the server.
composer require shipfastlabs/parsel
🔗 Full guide — complete API walkthrough, OCR setup, structured document parsing, streaming large files, the fake test runner, and real-world patterns.
#PHP #Laravel #OpenSource #DocumentParsing #PDF #OCR #BackendDevelopment #PHPDevelopment #WebDevelopment #SoftwareEngineering #BackendEngineering #Programming #TechCommunity #SoftwareDevelopment #CodeQuality #OpenSourceSoftware #DataExtraction #DocumentIntelligence #LaravelPHP #DevExperience https://t.co/xRN2H0aUyT
Unpopular opinion: most developers using AI for code review are doing it wrong.
They paste code and ask "is this good?"
That's like asking a doctor "am I healthy?" without any context.
The output is vague. The advice is generic. Nothing actually gets fixed.
Here's what works instead — give the AI a role, a standard to check against, and a required output format.
Example for security review:
- Role: "Act as a penetration tester, think like an attacker"
- Standard: "Check against OWASP Top 10 A01–A07"
- Output: "Return a table: Vulnerability | Severity | Line | Fix"
The difference in output quality is not subtle. It's 10x.
The structure that makes this repeatable is called CRTSE (Context, Role, Task, Standards, Output).
I've built 10 production-ready prompts using this format — for SQL, security, DevOps, and more.
Are you using a structured approach for AI prompts, or still winging it?
12 things developers still install npm packages for in 2026.
All of them have native replacements.
━━━━━━━━━━━━━━━━━━━━━━━━
uuid → crypto.randomUUID()
cloneDeep → structuredClone()
date format → Intl.DateTimeFormat
relative time → Intl.RelativeTimeFormat
debounce → 8 lines
flatten → .flat(Infinity)
groupBy → Object.groupBy()
uniq → new Set()
classnames → .filter(Boolean).join(' ')
range → Array.from({ length })
shuffle → Fisher-Yates (4 lines)
truncate → CSS text-overflow
━━━━━━━━━━━━━━━━━━━━━━━━
The three that surprise developers most:
1️⃣ structuredClone()
Handles Date, Map, Set, circular references, ArrayBuffer.
Faster than any JS implementation.
Baseline since 2022.
const copy = structuredClone(complexObject)
2️⃣ Object.groupBy()
One line. Baseline 2024.
Object.groupBy(orders, order => order.status)
// { pending: [...], shipped: [...], delivered: [...] }
3️⃣ crypto.randomUUID()
Cryptographically secure. Faster than any package.
Built into every modern browser and Node.js 14.17+.
crypto.randomUUID() // done
The question before every npm install:
Does the platform already do this?
Full guide with code for all 12 + when to keep the package.
#JavaScript #TypeScript #WebDevelopment #FrontendDevelopment #WebPerformance #BundleOptimization #Programming #SoftwareDevelopment #FrontendEngineering #TechCommunity #WebDev #OpenSource #VanillaJS #WebPlatform #CodeQuality #SoftwareCraft #100DaysOfCode #NodeJS #NPM #DevExperience https://t.co/LCcwhzb3xB
php devs, we no longer need to duct-tape python scripts just to parse a pdf 😭
launching Parsel: a fast memory efficient local document parser for PHP.
pdfs, office docs & images → text, structured data, bboxes, screenshots.
built for AI/RAG, NLP, invoices, search, and messy docs.
composer require shipfastlabs/parsel
I wasted 20 minutes writing this prompt.
You can copy it in 20 seconds.
Here's the exact AI prompt I use to audit SQL queries before they hit production:
---
[CONTEXT] I have a PostgreSQL query running on a 10M+ row table taking 3+ seconds.
[ROLE] Act as a senior DBA with 10 years of performance tuning experience.
[TASK] Audit this query: [PASTE QUERY]
[STANDARDS] Check for: missing indexes, sequential scans, N+1 patterns, type cast issues.
[OUTPUT] Give me: diagnosis, optimized rewrite, CREATE INDEX statements, and estimated % improvement.
---
Saved me 2 hours of EXPLAIN ANALYZE debugging last week.
I've built 10 more like this — covering security auditing, schema design, dependency scanning, and more.
They're packaged as a ready-to-use prompt pack for developers. Link in comments.
What's the most painful part of your database workflow? Drop it below — I'll share a prompt for it.
#AI #Database #postgreSql
The Composition API is better than Options API.
Better doesn't mean can't be done wrong.
Here are the mistakes I see most in Vue 3 codebases:
1. The 300-line script setup
The tool changed. The discipline didn't.
Four composable calls > three hundred mixed lines.
2. reactive() + destructuring
const { user } = state ← reactivity silently lost
No warning. No error. Just a template that won't update.
Fix: ref() by default. toRefs() if you must destructure.
3. onMounted + watch for the same data
Both fire on first load. Two fetches, one result.
Fix: watchEffect — runs on mount AND on change. One pattern.
4. Composables that leak
window.addEventListener in onMounted with no onUnmounted.
Every component mount adds a listener. None removed.
Rule: every add has a matching remove.
5. Computed with side effects
computed(() => { fetchUser(id.value) }) ← runs on every access
Computed is a pure function. API calls go in watchEffect.
6. Lifecycle hooks after an await
After the first await, setup context is gone.
Hooks called after an await are silently ignored.
Register all hooks before any async work.
The two rules that resolve most confusion:
ref() by default. reactive() for specific cases.
computed = pure derivation. watch = side effects.
Full guide with before/after code for all nine mistakes — link in comments.
#VueJS #Vue3 #CompositionAPI #TypeScript #JavaScript #FrontendDevelopment #WebDevelopment #VueMastery #FrontendEngineering #CodeQuality #SoftwareCraft #Programming #TechCommunity #WebDev #OpenSource #UIEngineering #DevExperience #100DaysOfCode #CleanCode #SoftwareDevelopment https://t.co/nEAz66YY2p
Caching is the single most impactful performance optimisation available to a Laravel application.
It's also the most misused.
Most developers know Cache::get and Cache::put. Far fewer understand the complete picture — the layers, how they interact, and the invalidation strategies that prevent your cache from becoming a source of bugs.
Here's the full stack 👇
⚡ Layer 1: Framework Caches (run on every deploy)
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache
No application code changes. Saves 5-15ms on every request. Route caching compiles all routes into one serialised file. Config caching merges all config files into one.
Two constraints nobody mentions:
→ Route caching breaks on closure routes — every route must point to a controller
→ Config caching breaks on env() calls outside config files — all env() must go through config()
🗂️ Layer 2: Query Caching
$products = Cache::remember('products:category:electronics', 3600, fn () =>
Product::where('category', 'electronics')->get()
);
Frequently read, infrequently changed data. Saves 5-100ms per request.
Design cache keys with a consistent convention:
products:all
products:category:{slug}
users:{id}:orders:{status}
A CacheKeys class centralises key generation and makes refactoring possible.
🏷️ Layer 3: Tag-Based Invalidation
The mistake most teams make: invalidating everything with 'product' in the key when one product changes. Correct intent, wasteful result.
Tags let you invalidate related entries as a group:
Cache::tags(['products', "category:{$slug}"])->remember(...);
// When a product changes:
Cache::tags(["product:{$id}"])->flush(); // just this product
Cache::tags(["category:{$slug}"])->flush(); // just this category
Cache::tags(['products'])->flush(); // all products
Requires Redis or Memcached. File driver doesn't support tags.
📄 Layer 4: Full-Page Response Cache
spatie/laravel-responsecache caches entire HTTP responses.
For a cached response: served in under 1ms. No application code, no database, no view layer.
Route::middleware(['cacheResponse:3600'])->group(fn () => {
Route::get('/products/{product}', ...);
Route::get('/blog/{post}', ...);
});
Public pages with no user-specific content. The biggest single-page performance win available.
❌ The one mistake that causes more cache bugs than any other
Update the data. Forget to invalidate the cache.
Users see stale data. The database is correct. The bug disappears when the TTL expires.
You can't reproduce it. It becomes a "known issue that only happens sometimes."
The fix: tie cache invalidation to model events, not to the code that updates models.
class ProductObserver {
public function saved(Product $product): void {
Cache::forget("products:{$product->id}");
Cache::tags(['products'])->flush();
}
}
Every update path — controller, import, job, command — automatically invalidates. You can't forget because the observer never forgets.
📖 Full guide — all five cache layers, Redis configuration, the cache warming strategy for deployment, complete invalidation patterns, the production checklist, and the Artisan commands reference.
#Laravel #PHP #Caching #Redis #BackendDevelopment #PHPDevelopment #WebDevelopment #SoftwareEngineering #LaravelPHP #BackendEngineering #Performance #WebPerformance #Programming #TechCommunity #SoftwareDevelopment #CodeQuality #SoftwareCraft #OpenSource #LaravelTips #Scalability https://t.co/TltaRXHNjT
Every Laravel tutorial covers the basics.
auth. verified. throttle.
These ship with the framework. Everyone knows them.
The middleware that actually separates hobby projects from production applications is rarely discussed. It doesn't add features. It's not glamorous. But it handles the real world — scrapers, brute-force attacks, proxy misconfiguration, SSL stripping, and the debugging gaps that make production incidents take days instead of hours.
Here are the seven layers every production Laravel app should have 👇
🔀 1. Trusted Proxy Configuration
Without this, when your app sits behind a load balancer or Cloudflare:
→ request()->ip() returns 10.0.0.1 (the proxy) instead of the real user IP
→ Rate limiting blocks the proxy instead of individual users
→ request()->secure() returns false even on HTTPS
→ Redirect URLs generate with http://
One env variable and four lines of config. Fixes all of it.
🔒 2. Force HTTPS
Not just redirect HTTP → HTTPS. Also:
URL::forceScheme('https') in AppServiceProvider so every generated URL, email link, and redirect uses HTTPS — even when SSL is terminated at the proxy layer.
⚡ 3. Advanced Rate Limiting
The built-in throttle middleware is one-dimensional. Production needs:
→ 120 requests/min for authenticated users, 20 for anonymous
→ Login endpoint: 5 attempts per IP, 3 per email+IP combo
→ Export/report endpoints: 10 per hour per user
Named rate limiters. Separate limits. Correct IP resolution (after trusted proxy).
🕭 4. Suspicious Request Detection
Every Laravel app running in production gets scanned for WordPress paths, .env files, phpMyAdmin, and vulnerability scanner fingerprints.
A middleware that returns 404 for these paths:
→ Reduces log noise immediately
→ Blocks scanner automation before it reaches application logic
→ Logs the attempts for monitoring
Returns 404, not 403. Don't confirm the path exists.
🛡️ 5. Security Headers
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Strict-Transport-Security: max-age=31536000; includeSubDomains
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
And Content-Security-Policy — start with report-only mode, collect violations for 2 weeks, then enforce. Zero performance cost. Significant browser-level protection.
🛠️ 6. Maintenance Mode Bypass Token
php artisan down --secret="your-token"
Your team visits https://t.co/Zq2NHvbRAF
They get a cookie. They can preview the application while it's down.
Everyone else sees the 503.
Distributed teams with dynamic IPs no longer need IP-based bypass lists.
📝 7. Request Logging
In production you cannot dd(). A middleware that logs:
→ All requests taking > 2000ms
→ All 4xx and 5xx responses
→ All POST requests from authenticated users
...with sensitive fields redacted and X-Request-Duration header for monitoring.
When a user reports a bug, the log entry is already there.
None of these require external packages. Each is under 50 lines. Together they're an afternoon of work that prevents incidents that would otherwise take days to diagnose.
📖 Full guide — complete implementation for all seven, the CSP rollout strategy, the trusted proxy security note, rate limiter configurations, and the complete production checklist.
#Laravel #PHP #WebSecurity #BackendDevelopment #PHPDevelopment #WebDevelopment #SoftwareEngineering #LaravelPHP #BackendEngineering #DevOps #Security #Programming #TechCommunity #SoftwareDevelopment #CodeQuality #SoftwareCraft #OpenSource #LaravelTips #ProductionReady #MiddleWare https://t.co/S2m4UWnpZb
I replaced 6 npm packages with 60 lines of JavaScript.
date-fns. lodash. axios. uuid. classnames. marked.
Here's what I actually found — including the one I had to revert 👇
✅ uuid → crypto.randomUUID()
One character replacement. Built into every modern browser and Node.js 14.17+. Cryptographically secure. Faster than the package. Strictly better in every way.
Before: import { v4 as uuidv4 } from 'uuid'
After: crypto.randomUUID()
Saved: 2KB. Zero caveats.
✅ axios → 25-line fetch wrapper
For standard JSON APIs, fetch handles everything axios was doing for us.
Base URL. Auth header injection. 401 handling. JSON parsing. Error normalisation.
25 lines. 1KB instead of 13KB.
The test suite didn't notice the difference.
✅ date-fns → Intl.DateTimeFormat + Intl.RelativeTimeFormat
Both APIs have been Baseline since 2019.
Better localisation than date-fns by default — respects user locale automatically, zero config.
format(date, 'MMM d, yyyy') → new Intl.DateTimeFormat('en-IN', { ... }).format(date)
formatDistanceToNow(date) → Intl.RelativeTimeFormat in 15 lines
Saved: 18KB. The output is actually better.
✅ lodash (debounce, groupBy) → native
debounce: 8 lines
Object.groupBy: 1 line (Baseline 2024)
Basic multi-field sort: 10 lines
Kept: _.cloneDeep, _.merge, _.get/set for nested paths. The deep utilities are worth keeping.
⚠️ classnames → 3-line clsx function
Works. But classnames is already 500 bytes.
Saving 200 bytes is probably not worth a custom function to maintain.
Optional at best.
❌ marked → I tried. I regret it.
I built a regex-based Markdown parser.
It worked for 90% of cases.
Nested lists broke. Code blocks with backtick content corrupted output.
Two weeks later I reverted.
22KB because Markdown parsing is genuinely complex.
The complexity is in the package for a reason.
What I did instead: code-split it. 22KB only loads when a user opens a notification with Markdown. Which is rare.
The rule that actually matters:
Ask why the package is the size it is before deciding to replace it.
debounce is 8 lines because debounce is simple.
Marked is 22KB because Markdown is not.
Final numbers:
Before: ~63.5KB gzipped across 6 packages
After: ~4.8KB (keeping marked, code-split)
Saved: ~59KB
The four replacements that are almost always worth it:
→ uuid → crypto.randomUUID()
→ axios (simple JSON APIs) → fetch wrapper
→ basic date formatting → Intl.DateTimeFormat
→ individual lodash utilities → 10 lines each
📖 Full breakdown — every replacement with complete code, the marked mistake, the framework for deciding which packages to replace, and the honest verdict.
#JavaScript #WebDevelopment #FrontendDevelopment #BundleOptimization #WebPerformance #TypeScript #FrontendEngineering #Programming #SoftwareDevelopment #TechCommunity #WebDev #OpenSource #NPM #VanillaJS #WebPlatform #CodeQuality #SoftwareCraft #100DaysOfCode #CoreWebVitals #DevExperience https://t.co/GVHPsT4Srw
introducing laravel moat
as an open source maintainer, recent supply chain attacks in the ecosystem made me want a simple cli to audit the security of my GitHub organizations and repositories
built in Rust. for any open source project on GitHub
Every Vue developer knows v-model.
You use it on inputs. You know it's shorthand for :modelValue and @update:modelValue. You move on.
That's the 20%.
Here's the other 80% 👇
⚡ defineModel() — Vue 3.4+ eliminates all the boilerplate
// ✗ Old pattern — 4 things every single time
const props = defineProps<{ modelValue: string }>()
const emit = defineEmits<{ (e: 'update:modelValue', v: string): void }>()
// + bind :value, + emit on @input
// ✓ Modern pattern — one line
const model = defineModel<string>()
That's it. The prop, the emit, the binding — all handled.
📋 Multiple named v-models — the underused gem
// Parent
<DateRangePicker
v-model:startDate="filterStart"
v-model:endDate="filterEnd"
/>
// Component
const startDate = defineModel<string>('startDate')
const endDate = defineModel<string>('endDate')
One component. Two independent two-way bindings. No prop drilling.
🔄 defineModel() with get/set — transparent value transforms
// Parent stores paise. Input shows rupees. Zero boilerplate.
const model = defineModel<number>({
get(paise) { return paise / 100 },
set(rupees) { return Math.round(rupees * 100) },
})
Same pattern for: phone number masking, null ↔ empty string, slug generation, clamped number inputs.
🎛️ Custom v-model modifiers — define your own transformations
// Parent
<CustomInput v-model.capitalize="username" />
// Component
const [model, modifiers] = defineModel<string>({
set(value) {
if (modifiers.capitalize)
return value.charAt(0).toUpperCase() + value.slice(1)
return value
},
})
The modifier is just a boolean in the modifiers object. Apply whatever logic you need.
🌲 provide/inject for deeply nested forms
When a form field is three components deep, emit chains and prop drilling become unmanageable.
The solution: FormProvider provides the form state via inject/provide. FormField anywhere in the tree injects it and binds directly. No prop threading. No emit chains.
📦 Wrapping third-party inputs
Most libraries don't use Vue's modelValue/update:modelValue convention.
Wrap them once in a defineModel() component and the rest of your app uses standard v-model syntax. The wrapper handles the translation.
The cheat sheet you'll use daily:
v-model → prop: modelValue
v-model:title → prop: title
v-model.trim → built-in modifier
v-model.capitalize → your custom modifier
defineModel() → unnamed, default model
defineModel('title') → named model
defineModel({ get, set }) → with transforms
[model, modifiers] → access modifier flags
📖 Full guide — defineModel() options, multiple named bindings, custom modifiers, the get/set transform pattern, checkbox groups, rich selects, the provide/inject form pattern, and wrapping third-party inputs.
#VueJS #Vue3 #CompositionAPI #TypeScript #JavaScript #FrontendDevelopment #WebDevelopment #VueMastery #FrontendEngineering #UIEngineering #Programming #SoftwareDevelopment #TechCommunity #WebDev #OpenSource #DevExperience #CodeQuality #SoftwareCraft #100DaysOfCode #Forms https://t.co/9EKKjIZY2G
Most race conditions don't appear in development.
You test with one browser, one user, one request at a time. Everything works perfectly.
Then production: a hundred users hit the same endpoint simultaneously. Inventory goes negative. Coupons get used twice. Users are charged twice. The requests completed. The database shows the wrong state. Nobody can reproduce it.
Race conditions in Laravel follow predictable patterns. Here are the five most common — and the exact fix for each 👇
📦 The inventory oversell
if ($product->stock >= $qty) {
$product->decrement('stock', $qty); // two requests both pass the if
}
Request A reads stock = 1. Request B reads stock = 1. Both pass. Both decrement. Stock: -1.
Fix — atomic WHERE condition:
$updated = Product::where('id', $product->id)
->where('stock', '>=', $qty)
->decrement('stock', $qty);
if ($updated === 0) throw new InsufficientStockException();
The check and the decrement happen in one SQL statement. The database serialises them.
🎟 The coupon used twice
Same pattern. if (used_count < max_uses) → increment. Two requests both pass. A single-use coupon gets used twice.
Fix: whereColumn('used_count', '<', 'max_uses')->increment('used_count')
👤 The duplicate subscription
User clicks "Subscribe" twice quickly. Two requests both check: does subscription exist? Both find: no. Both create one. User now has two active subscriptions and two charges.
Fix:
Cache::lock("subscription:{$userId}:{$planId}", 10)->block(5, function () {
// Only one request enters here at a time
if (subscription exists) throw AlreadySubscribedException
create subscription
})
💰 The wallet double-spend
Two concurrent withdrawals both read balance = 100. Both check: 100 >= 60 ✓. Both deduct 60. Balance: -20.
Fix — pessimistic locking:
DB::transaction(function () use ($user, $amount) {
$wallet = Wallet::where('user_id', $user->id)
->lockForUpdate() // SELECT ... FOR UPDATE — other requests wait
->first();
if ($wallet->balance < $amount) throw InsufficientFundsException;
$wallet->decrement('balance', $amount);
});
🔑 The database unique constraint — your last line of defence
Application locks can fail. Network can split. Processes can die mid-operation.
For anything that must be unique — one subscription per user per plan, one vote per post — a database unique constraint is non-negotiable. It rejects duplicates even when everything else fails.
$table->unique(['user_id', 'plan_id']);
The code review question that finds race conditions before production does:
"What happens if two requests execute this simultaneously?"
Every if() checking a database value before writing. Every counter that reads then increments. Every firstOrCreate() without a unique constraint. These are the places to look.
firstOrCreate() is NOT atomic. It's a SELECT then INSERT. Two requests can both SELECT with no result and both INSERT. Always pair with a unique constraint.
📖 Full guide — all five patterns, atomic operations, pessimistic locking, optimistic locking with version columns, Cache::lock() for distributed coordination, deadlock prevention, and the complete audit checklist.
#Laravel #PHP #BackendDevelopment #DatabaseDesign #Concurrency #PHPDevelopment #WebDevelopment #SoftwareEngineering #LaravelPHP #BackendEngineering #SoftwareArchitecture #Programming #TechCommunity #SoftwareDevelopment #CodeQuality #SoftwareCraft #OpenSource #Performance #DatabaseOptimization #LaravelTips https://t.co/AGbMuUhSCt
🚨 Ongoing supply chain attack on Composer packages! We just found multiple laravel-lang/* packages compromised on Packagist (lang, http-statuses, attributes). Payload runs at autoload time. At least 50 package versions were compromised.
If you installed a compromised version, the malware already executed. Pin to a clean COMMIT (not version) and rotate secrets immediately.
If your lockfile already had an older commit from before today, you are safe. But you should not update at the moment.
Every team has a file nobody wants to open.
In Vue projects, it's always the same pattern.
A component that started as 40 lines. Fetches data. Renders a list. Fine.
Then someone adds pagination. Then sorting. Then a modal. Then bulk selection. Then filters with URL sync. Then debounced search.
Six months later: 847 lines. 23 reactive variables. 14 computed properties. Every change requires reading half the file to understand the context. Every bug fix risks breaking something else because everything is entangled.
Nobody wrote an 847-line component on purpose.
The mistake is not the size. It's the reason for the size: four distinct concerns living in one file.
1. Data fetching and async state
2. UI state (modals, selected items, active tabs)
3. Business logic (filtering, sorting, URL sync)
4. Rendering
When these four concerns share a file, they corrupt each other. A rendering concern leaks into business logic. A data concern pulls in UI state. Everything is connected to everything.
The decomposition that actually works:
✓ useProducts — owns the API call, loading/error state, and the data
✓ useProductFilters — owns filter state, debouncing, and URL sync
✓ useProductSelection — owns selected IDs and selection operations
✓ useProductModals — owns which modals are open and with what data
Each composable owns one domain. None know about each other.
useProductFilters doesn't know that useProducts will consume its state.
useProductSelection doesn't know about the URL.
useProductModals doesn't know about the API.
The parent component wires them together:
const filters = useProductFilters()
const data = useProducts(filters)
const selection = useProductSelection(data.products)
const modals = useProductModals()
65 lines. The template reads like documentation.
The composables are independently testable.
The child components have clear, narrow responsibilities.
The rule that decides composable vs child component:
Does the extracted logic render markup?
Yes → child component
No → composable
The signal to act before it's too late:
When you hesitate before opening a file because you know it'll take ten minutes just to orient yourself — that hesitation is the cost of deferred decomposition.
Extract the composable at 200 lines. Not 800.
📖 Full guide — the god component anatomy, all four composable extractions with complete TypeScript, the slim parent orchestrator pattern, child component boundaries, the too-many-props warning, and the incremental refactor strategy.
#VueJS #Vue3 #CompositionAPI #TypeScript #JavaScript #FrontendDevelopment #WebDevelopment #VueMastery #FrontendEngineering #SoftwareArchitecture #ComponentDesign #Programming #SoftwareDevelopment #TechCommunity #WebDev #OpenSource #UIEngineering #CodeQuality #SoftwareCraft #CleanCode https://t.co/1b3RGOPJnq
Most Laravel developers have the same Telescope story.
Install it. Visit /telescope once. Confirm it works. Close the tab.
Go back to dd() and Log::info() for every bug.
This is like buying a Formula 1 car to pick up groceries.
Telescope is the most complete real-time introspection tool available for Laravel. Every HTTP request, every database query, every queued job, every exception, every cache operation, every mail message — all of it captured with full context, filterable, searchable, and cross-linked.
Here's what debugging actually looks like when you use it properly 👇
📊 Requests Watcher — start every debugging session here
A request showing 47 queries tells you there's an N+1 problem before you've written a single line of debugging code. Duration breakdown tells you whether the bottleneck is queries or PHP execution. The response body is there. The payload is there. Every linked query, exception, and job is one click away.
🔍 Queries Watcher — N+1 problems become undeniable
Every query shows the full SQL with bound values, execution time, and the exact file and line that triggered it. Queries above your slow threshold are highlighted red. Duplicate queries with the same structure but different WHERE id = ? values are your N+1 — visible instantly, no reproduction needed.
📧 Mail Watcher — no more Mailtrap
Every email is captured before sending with a full HTML preview. User says they never got the confirmation email? Find the mail entry in two seconds. If it's there: the email was sent, check their spam. If it's not: the code path that sends it was never executed.
⚙️ Jobs Watcher — background jobs are no longer invisible
Failed job? The exception, stack trace, payload, and every query the job ran before failing are all there. No "add log statements, re-queue, wait, parse output" cycle. The full context is already recorded.
💥 Dumps Watcher — stop using dd()
dump() writes to Telescope. The request continues. You see the value in a formatted UI instead of raw PHP output in a broken API response. Use dump() instead of dd() in any context that matters.
The debugging workflow that saves the most time:
User reports: "email not arriving."
→ Requests tab — find their POST /api/orders
→ Check linked jobs — was a notification queued?
→ Jobs tab — did it complete or fail?
→ Failed job detail — full exception + payload
→ Root cause found in under 5 minutes
Without Telescope: add log statements, reproduce the exact user state, trigger the flow, parse the logs, repeat.
The configuration that makes it actually useful:
→ Ignore health check paths (they flood the Requests tab)
→ Add tags to jobs and requests (filter by user:42 to watch one user)
→ Set slow query threshold to 50ms (highlights problems automatically)
→ In staging: filter to only watch specific QA tester emails
📖 Full guide — installation, watcher configuration, filtering, tag-based debugging, the Mail/Jobs/Cache/Schedule watchers, production usage, and a complete real debugging walkthrough.
#Laravel #PHP #Debugging #LaravelTelescope #BackendDevelopment #PHPDevelopment #WebDevelopment #SoftwareEngineering #LaravelPHP #BackendEngineering #DeveloperTools #Programming #TechCommunity #SoftwareDevelopment #CodeQuality #DevExperience #SoftwareCraft #OpenSource #DeveloperExperience #LaravelTips https://t.co/Jma0xci1Cp
The JavaScript ecosystem has a reflexive response to almost every UI requirement.
Need to detect when an element enters the viewport? Install a package.
Need a tooltip? Install Floating UI.
Need to highlight search results? Install mark.js.
Need page transitions? Install a routing animation library.
Most of the time, that response is wrong.
The browser already ships the answer. And has for years.
Five native APIs that eliminate entire npm packages 👇
🔭 1. IntersectionObserver
Replaces: scroll event listeners, getBoundingClientRect() loops, is-in-viewport packages
Scroll listeners fire 60+ times per second. getBoundingClientRect() triggers layout on every call. Both run on the main thread.
IntersectionObserver runs off the main thread. The callback fires only when elements cross a threshold. Lazy loading, infinite scroll, scroll-triggered animations — all of it, zero main thread blocking.
Baseline: Widely Available since 2018. There is no reason to use a scroll listener for this.
🎬 2. View Transitions API
Replaces: GSAP page transitions, swup, barba.js, custom fade/slide implementations
document.startViewTransition(() => updateUI())
That one line gives you a cross-fade between any two DOM states. With view-transition-name, specific elements animate independently — the "shared element" transition where a product card image flies to the detail page.
No animation code in your components. No library. The browser handles position, size, and timing.
Baseline: Widely Available since October 2025 — Chrome, Edge, Firefox 133+, Safari 18+.
🎨 3. CSS Custom Highlight API
Replaces: mark.js, DOM-wrapping search highlight libraries
The old way: wrap every match in a <span> or <mark>. Breaks when the match crosses element boundaries. Mutates the DOM. Slow on large documents.
The new way: create Range objects in JavaScript, register them as a named highlight, style with CSS. The DOM is never touched.
::highlight(search-results) { background-color: #ffdd57; }
Highlights work across element boundaries. Clearing is instant. No DOM mutations, ever.
Baseline: Widely Available since June 2025.
💬 4. Popover API
Replaces: Floating UI, Tippy.js, Popper.js, custom focus trap logic
<button popovertarget="menu">Settings</button>
<div id="menu" popover>...</div>
Zero JavaScript. The browser handles: z-index via the top layer, outside-click dismissal, Escape key, focus management, ARIA semantics.
Baseline: Widely Available since April 2025.
⚡ 5. Speculation Rules API
Replaces: quicklink, https://t.co/8rHbL6jqKm, custom prefetch scripts
Pre-renders pages before the user navigates. The destination appears in ~50ms instead of 1000ms. No code changes needed on the target page.
<script type="speculationrules">
{ "prerender": [{ "urls": ["/checkout"], "eagerness": "moderate" }] }
</script>
Chrome and Edge. Progressive enhancement only — Firefox and Safari don't support it yet.
The audit that saves the most time:
✓ getBoundingClientRect() in a scroll listener? → IntersectionObserver
✓ Page transition library in package.json? → View Transitions API
✓ mark.js or highlight library? → CSS Custom Highlight API
✓ Tippy.js or Floating UI? → Popover API
✓ quicklink or https://t.co/8rHbL6jqKm? → Speculation Rules
Zero bundle cost. Better performance. Less maintenance. The packages you remove are performance you keep forever.
📖 Full guide — complete code examples for all five APIs, the progressive enhancement pattern, browser support table, and the full audit checklist.
#JavaScript #WebDevelopment #FrontendDevelopment #BrowserAPIs #WebPerformance #FrontendEngineering #TypeScript #Programming #SoftwareDevelopment #TechCommunity #WebDev #BundleOptimization #WebPlatform #CSS #OpenSource #UIEngineering #DevExperience #100DaysOfCode #CoreWebVitals #ProgressiveEnhancement https://t.co/Za9mq6p9Gv