vercel

Stars and mentions

Last 30 days, Oct 4 – Nov 2

GitHub stars2,245
mentions108
057114171228Oct 4Oct 18Nov 2
UTC days · repositories vercel owns27 repositories

Package downloads

Cumulative recorded downloads · since 2025-10-18

crates.io—
npm254.6B
063.6B127.3B190.9B254.6BOct 4Oct 18Nov 2
Oct 29crates.io: 0/5 packages observed · npm: 22/126 packages observed

Across 131 known packages in repositories vercel owns. Cumulative totals include recorded downloads before the selected range. Missing observations leave gaps; totals may be incomplete.

Top repositories

vercel’s repositories ranked by pull request authors, as on trending.

Loading

Contributes to

Repositories outside vercel’s account where vercel opened or reviewed pull requests in the past 365 days.

Who noticed vercel

Everyone who starred or forked vercel’s repositories since the archive began in 2011.

Notable accounts

Ordered by the stars their own repositories have received.

Biggest fans

Accounts that starred the most of these repositories.

Breakout weeks

Each repository’s best week of new stars, with the release published around it.

Mentions

109posts
Nov 32 posts
  1. i made 3 open source contributions to libraries in a few days. 1. i was looking into some very performant markdown rendering libraries for streaming content and came across streamdown. made 2 contributions - a bug fix and perf improvement. streamdown#685 streamdown#689 2. i was using workflow library for a project when claude discovered a bug. workflow#4932 a few notes: - i enjoyed learning the internals so much, especially now that ai makes it so easy and i can ask unlimited questions - after knowing a bit more about the internals, it was so easy to have claude run my perf improvement idea quickly and check if it was any good - yes it's relatively easy to make oss contributions now but that doesn't mean one should make the job difficult of maintainers by putting up PRs without understanding and with descriptions that even they don't understand - i actually had claude generate specific figures that i could use in my PRs - it is your duty to make it easier for maintainers to review it - @vercel is doing so good when it comes to oss - they are shipping all the time

    6 likes · 318 views
  2. Next.js 16 が publicRuntimeConfig を廃止予告 — 3 アプリへの影響確認と移行パターン Cosoado Lab Blog 同時掲載予定: cosoado-lab.com 以前「Next.js で env var 1 つで 3 アプリの配色・機能を切り替えるジャンル設計」を書いた。Next.js 16 canary のリリースノートを眺めていたら Remove deprecated publicRuntimeConfig and serverRuntimeConfig というタイトルのコミットが目に入って、一瞬血の気が引いた。「あの記事の前提、詰んだ?」と思ったので、3 アプリ分のコードを確認しながら移行パターンを整理した。 TL;DR Next.js 16.0.0 では削除は revert されており、deprecation 警告の追加に留まっている canary ブランチのアップグレードガイドには「removed. Use environment variables instead」と明記されており、削除は時間の問題 移行パターン 2 種: ① ビルド時定数 → NEXT_PUBLIC_* ② 実行時読み取り → process.env + connection() getConfig() を呼んでいるコードがあれば今すぐ grep して棚卸しした方がいい publicRuntimeConfig が何のために存在したか 存在意義は「ビルド後に値を変えられる」こと。NEXT_PUBLIC_* はビルド時に bundle に静的インライン展開されるので、値を変えるには再ビルドが必要だ。getConfig() を使う runtime config は、サーバー起動時の process.env を変えるだけで差し替えられた。App Router 以降は Server Components が増えてこのニーズが別の方法で満たせるようになり、廃止に向かっている。 v16.0.0 の実際の状況 canary では PR #83944 で削除が一度 merge されたが、リリース直前の PR #84167 で revert された。v16.0.0 には削除ではなく PR #84168 で追加された deprecation 警告だけが入っている。 ただし canary ブランチのアップグレードガイドには: serverRuntimeConfig and publicRuntimeConfig have been removed. Use environment variables instead. と書いてある。v16.0.0 に間に合わなかっただけで削除の意志は変わっていない。次のマイナー更新か 16.x のどこかで壊れる。 私の 3 アプリは影響ゼロだった SparMate・NetaPair・BoardLink は全部 NEXT_PUBLIC_GENRE=sparmate のような NEXT_PUBLIC_* パターンを使っていた。getConfig() はどこにも呼んでいなかった。 確認するまでの 10 分は冷や汗をかいたが、前回の記事を書いたときに「runtime config は使わない」と決めていたのが功を奏した。ちゃんと理由を持って設計しておくとこういうとき助かる。 移行パターン 2 種 パターン 1: ビルド時定数 → NEXT_PUBLIC_* 値はビルド時に bundle に埋め込まれる。 // Vercel の Environment Variables に NEXT_PUBLIC_API_BASE_URL を追加して置き換えるだけ const apiUrl = process.env.NEXT_PUBLIC_API_BASE_URL 値を変えると再ビルドが必要。「デプロイ後に差し替えたい」ニーズがあればパターン 2 を使う。 パターン 2: 実行時読み取り → process.env + connection() Server Components 専用。connection() を先に呼ぶことで「プリレンダリング時ではなく、リクエスト時に読む」ことが保証される。 import { connection } from 'next/server' export default async function Page() { await connection() // これ以降はリクエスト時に実行される const apiUrl = process.env.API_BASE_URL // ... } connection() なしだと、Next.js が静的プリレンダリング時に process.env を評価してしまう場合がある。serverRuntimeConfig の「起動後に差し替えたい」ユースケースはこれで代替できる。 やらかした話: connection() を Client Component で呼ぼうとした 移行パターンを確認しながら「connection() を useEffect の中で呼べばいいのでは?」と試した。コンパイルは通ったが、ブラウザのコンソールに「connection is not defined in client context」みたいなエラーが出た。 connection() は Server Components 専用だ。'use client' の中では呼べない。実行時読み取りが必要な値は Server Component で取得して props で渡すか、Server Action 経由にする必要がある。「async/await が書けるなら Server Component でも Client Component でも同じか」という油断がある。これは一番やった間違いで、自信満々に修正して PR を出したあとにレビューで気づいた。 まとめ getConfig() を使っているコードは今すぐ grep -r "next/config" して棚卸しを v16.0.0 は deprecation 警告だけだが、canary docs には「removed」とある ビルド時定数でよければ → NEXT_PUBLIC_* デプロイ後に変えたい実行時値は → Server Component + connection() + process.env getConfig() を使った例は古いドキュメントやチュートリアルにまだ多い。次のアップグレードで詰まる前に確認しておく価値がある。 参照: vercel/next.js #83944 — Remove deprecated publicRuntimeConfig and serverRuntimeConfig vercel/next.js #84167 — Revert removal for v16.0.0 vercel/next.js #84168 — Add deprecation warning to Runtime config Next.js v16 アップグレードガイド (canary) SparMate — 格闘技の練習相手マッチング NetaPair — お笑いの相方探し BoardLink — ボドゲ・TRPG 仲間募集 Cosoado Lab

  3. 写 AI 应用最烦的,是每个厂商 SDK 各写一套。 Vercel AI SDK 7.x 一个统一接口接多家厂商,TypeScript 写多模型产品,换厂商不用重写。 适合做多模型产品的 TypeScript 开发者,先从一个厂商接入试起。 接多家模型,自己封装还是用 SDK? #AI开发 #开源 @grok vercel/ai

    5 likes · 1 repost · 233 views
  4. Prerendering a page per device, and the cache key that has to agree --- title: "Prerendering a page per device, and the cache key that has to agree" published: true description: "A prerendered page in the Next.js App Router is one file per URL, and device specific markup needs two. Generating the extra routes keeps the public URL intact. Making your device split agree with the cache in front of it is the part nobody warns you about." tags: nextjs, webdev, caching, performance canonical_url: yhryshchanka.dev ai_disclosure_level: some_ai --- A marketing page with no per visitor content should be built once and handed out as a file, which is what prerendering means here: the build writes the HTML, and the server answers with it instead of rendering again. The page I had to do this to was not quite static. Its footer is an accordion on a phone and a grid on a laptop, and a few sections below it differ the same way. Two rendered versions of one document have to exist at the same time. The browser can make that choice by itself, which is the first thing to rule out. A CSS media query renders both versions and hides one, so the same text sits in the document twice and every visitor downloads the one they will never see. JavaScript picks after the page has loaded, so the first paint is a guess and the reader watches it change. Either way the page ships markup the device in front of it will not use. Choosing at build time means one version per file, and the HTML a crawler reads is the HTML a phone gets. Everything below happens inside the application, in the Next.js App Router, on version 15. The same split can live a layer above it instead. Inside the application, that means two files and one URL. A shared cache in front of it keys on that URL and nothing else, so on its own it cannot tell the two files apart. What it can do instead is split its own storage by device, and making my split agree with its split decided the shape of everything else. The whole thing, before the explanation Two prerendered files per page, one public URL, and a rewrite that picks between them before the request reaches a route. Everything below this section is the reasoning. This is the code, and it runs. A page opts in with one export, and refuses to render at its own path, where no device arrives: app/terms-of-use/page.tsx import Layout from '@/app/_layout/Layout' import type { DeviceSegment } from '@/lib/device' import { notFound } from 'next/navigation' export const isDeviceBasedSSG = true export const metadata = { title: 'Terms of use' } export default function TermsPage({ device }: { device?: DeviceSegment }) { if (!device) notFound() return <Layout device={device}>Terms of use</Layout> } A script turns that marker into one route per device, plus the list of paths that got one: scripts/generate-device-routes.mjs import { mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs' import { dirname, join, relative } from 'node:path' const DEVICES = ['desktop', 'mobile'] const SOURCE_DIR = 'app' const OUT_DIR = join('app', '(generated)') const PATHS_FILE = 'lib/cacheable-paths.generated.ts' const MARKER = /export\s+const\s+isDeviceBasedSSG\s*=\s*true/ // A folder named `(group)` adds no segment to the URL. A folder named `_private` is out of // routing altogether, so a page under one is not a route and must not get a wrapper. const isGroup = (name) => name.startsWith('(') const isPrivate = (name) => name.startsWith('_') function findMarkedPages(dir, routePath = '') { const entries = readdirSync(dir, { withFileTypes: true }) const here = entries.some((entry) => entry.isFile() && entry.name === 'page.tsx') && MARKER.test(readFileSync(join(dir, 'page.tsx'), 'utf8')) ? [{ routePath: routePath || '/', dir }] : [] return [ ...here, ...entries .filter( (entry) => entry.isDirectory() && !isPrivate(entry.name) && join(dir, entry.name) !== OUT_DIR, ) .flatMap((entry) => findMarkedPages( join(dir, entry.name), isGroup(entry.name) ? routePath : `${routePath}/${entry.name}`, ), ), ] } // The import path goes into a string literal and never into a comment: a directory // name may carry a line terminator, which ends a // comment and drops the rest into // code the build compiles. const wrapper = (importPath, device, metadataExport) => `// Generated. Do not edit. import Page${metadataExport ? `, { ${metadataExport} }` : ''} from ${JSON.stringify(importPath)} export const dynamic = 'error' ${metadataExport ? `\nexport { ${metadataExport} }\n` : ''} export default function GeneratedDeviceRoute() { return <Page device="${device}" /> } ` const marked = findMarkedPages(SOURCE_DIR) for (const { routePath, dir } of marked) { // A wrapper is a route at a fixed path, so it cannot carry a parameter, and it sits // directly under the app root, so it cannot inherit a layout from the source's folder. if (routePath.includes('[')) { throw new Error(`${dir}: a device route cannot hold a dynamic segment (${routePath})`) } const ancestors = routePath.split('/').filter(Boolean).slice(0, -1) for (let i = 0; i < ancestors.length; i += 1) { const layout = join(SOURCE_DIR, ...ancestors.slice(0, i + 1), 'layout.tsx') try { readFileSync(layout) throw new Error(`${dir}: ${layout} would not apply to the generated route`) } catch (error) { if (error.code !== 'ENOENT') throw error } } } rmSync(OUT_DIR, { recursive: true, force: true }) for (const { routePath, dir } of marked) { const source = readFileSync(join(dir, 'page.tsx'), 'utf8') const metadataExport = /export\s+(async\s+)?function\s+generateMetadata/.test(source) ? 'generateMetadata' : /export\s+const\s+metadata\b/.test(source) ? 'metadata' : '' for (const device of DEVICES) { const outFile = join(OUT_DIR, device, routePath, 'page.tsx') const importPath = relative(dirname(outFile), join(dir, 'page')).split('\\').join('/') mkdirSync(dirname(outFile), { recursive: true }) writeFileSync(outFile, wrapper(importPath, device, metadataExport)) } } writeFileSync( PATHS_FILE, `// Generated. Do not edit.\nconst CACHEABLE_PATHS = ${JSON.stringify( marked.map((page) => page.routePath), )} as const\n\nexport default CACHEABLE_PATHS\n`, ) console.log(`generated ${marked.length * DEVICES.length} routes for ${marked.length} pages`) Run it before anything that reads the routes, and keep what it writes out of the repository: package.json { "scripts": { "generate:routes": "node scripts/generate-device-routes.mjs", "predev": "npm run generate:routes", "prebuild": "npm run generate:routes", "pretypecheck": "npm run generate:routes", "pretest": "npm run generate:routes" } } .gitignore # Written by scripts/generate-device-routes.mjs. app/(generated)/ lib/cacheable-paths.generated.ts Middleware rewrites the public path into the generated route, so the visitor's URL and the canonical tag never carry a device: middleware.ts import CACHEABLE_PATHS from '@/lib/cacheable-paths.generated' import { deviceFromHeaders, isInternalDevicePath } from '@/lib/device' import { NextResponse, type NextRequest } from 'next/server' export function middleware(request: NextRequest) { const { pathname } = request.nextUrl // Nothing links to the internal form, so a request carrying one was typed or crawled. if (isInternalDevicePath(pathname)) { return NextResponse.rewrite(new URL('/_not-found', request.url), { status: 404 }) } if (!(CACHEABLE_PATHS as readonly string[]).includes(pathname)) return NextResponse.next() return NextResponse.rewrite( new URL(`/${deviceFromHeaders(request.headers)}${pathname}`, request.url), ) } export const config = { matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'], } One device read that every page shares, and the guard the middleware just used: lib/device.ts import { userAgent } from 'next/server' export const DEVICE_SEGMENTS = ['desktop', 'mobile'] as const export type DeviceSegment = (typeof DEVICE_SEGMENTS)[number] const LAYOUT_BY_CDN_TYPE: Record<string, DeviceSegment> = { mobile: 'mobile', tablet: 'mobile', desktop: 'desktop', } export function deviceFromHeaders(headers: Headers): DeviceSegment { const cdnType = headers.get('cf-device-type') ?? '' // Own keys only: outside a cache rule this header is whatever the client sent, and // `constructor` or `__proto__` would otherwise resolve to a member of the prototype. if (Object.hasOwn(LAYOUT_BY_CDN_TYPE, cdnType)) return LAYOUT_BY_CDN_TYPE[cdnType] return userAgent({ headers }).device.type === 'mobile' ? 'mobile' : 'desktop' } export function isInternalDevicePath(pathname: string): boolean { const [, first] = pathname.split('/') return (DEVICE_SEGMENTS as readonly string[]).includes(first) } And the response headers, because a prerendered route otherwise advertises a year of shared caching to whatever sits in front of it: next.config.mjs import CACHEABLE_PATHS from './lib/cacheable-paths.generated.ts' /** @type {import('next').NextConfig} */ export default { async headers() { return CACHEABLE_PATHS.flatMap((path) => [ { source: path, has: [{ type: 'header', key: 'rsc' }], headers: [{ key: 'Cache-Control', value: 'private, no-store' }], }, { source: path, missing: [ { type: 'header', key: 'rsc' }, { type: 'query', key: '_rsc' }, ], headers: [{ key: 'Cache-Control', value: 'public, max-age=0, s-maxage=3600' }], }, ]) }, } That is the implementation, on Next 15 with the App Router. Next 16 renames the file to proxy.ts and the function to proxy, which I have not run. The rest of this article is why each of those decisions is the one that survived, and the two failures none of the code shows on its face: a build that stays green while nothing is prerendered, and a cache that hands one visitor's layout to everybody else. The cache key is the path, and nothing from the request Next keeps the HTML it prerendered in what it calls the full route cache. The key for that cache is the resolved route path. In 15.5 it is one line of next/dist/server/base-server.js: let resolvedUrlPathname = getRequestMeta(req, 'rewroteURL') || urlPathname and getFilePath in the file system cache turns that same string into a path on disk. Nothing else from the request reaches it. Next does send a Vary header for whatever caches the response downstream, but its own route cache does not consult one. The query string does not help either. I measured that rather than reasoning about it: with the page not reading searchParams, ?device=m, ?device=d and no query at all came back byte for byte identical, all three hashing to 4005cda574c9, one cache entry answering every one of them. A page that reads searchParams is not prerendered at all, and whether that is a build failure or a silent fallback to rendering on demand is the subject of a later section. Rewrites declared in next.config do not create routes either, they point at routes that already exist, so they add no dimension to the key. I pointed one at a prerendered route from beforeFiles and another from afterFiles, and both source URLs came back identical to the route itself, all three hashing to d0fa5dfdb35c. One entry, three ways in. Two variants therefore mean two paths, and the only varying input a prerendered render can see is a route parameter. Where the shape came from I did not invent this. The shape comes from a comment by a Next collaborator in a discussion about serving a light and a dark theme out of one cache: > Prerender per known theme > > generateStaticParams returns the theme list. Pages live under [theme]/. A > proxy rewrites /foo → /<cookie>/foo server-side, and redirects direct > visits to /light or /dark back to the unprefixed path so themed URLs > aren't shareable. > > Each route ends up with one cache slot per theme. > > HTML arrives themed. No FOUC, no JS required. > > Server can branch on theme, that is, RTL, different components, distinct meta. Read it with devices in place of themes and it says four things. Make the variant a route parameter. Move the pages that want variants under that parameter's folder. Let generateStaticParams prerender one copy per value. Have middleware, the function that runs before a request reaches a route, rewrite the public URL into the variant so the visitor never sees it. He names two costs: > Switching theme is a server round trip, an actual navigation. > > Proxy on every request, between you and your CDN. Neither reaches this case. Nobody switches devices, so there is no switch to pay for. And middleware was already running on every request here, for locale routing and for putting visitors into experiments, so its cost was on the bill before this feature existed. Two parts of that I did not want. A dynamic segment in the first position costs more than the URLs it agrees to match. Once the first segment is a parameter, every first level path is a valid route, and the framework's own not found stops firing for a wrong one, because from its side nothing is wrong. Something inside the tree has to decide which values are real and call notFound() for the rest, which makes that check the only place the legal values are written down. A reader who wants to know what the site accepts can no longer look at the folders, they have to open the code. A whitelist does not rescue it either, because Next has no fall through: once a route matches, notFound() renders the not found boundary rather than handing the request to the next candidate route. The shared parent folder fails more quietly. Every page that wants a variant has to move under it, and pages that cannot have one stay where they are. Say articles/ holds two pages, one that can be cached and one that reads a cookie and therefore cannot. The first moves under [theme]/ and the second does not, so articles/ now exists in two places at once. Add a page, and remember the second tree. Rename a folder, and remember it again. The build is green either way, and the mistake shows up as a page that quietly stopped being prerendered. So the parameter went away, and two literal folders replaced it, desktop/ and mobile/, each holding a real page file. A folder matches its own name and nothing else, so an unknown value now matches no route at all. That is what generateStaticParams and dynamicParams were there to arrange, and what the guard was there to catch when they failed, so all of it goes. What literal folders do not remove is the duplicated tree. They move it: desktop/ and mobile/ each hold a copy of every marked page. What changes is who keeps the copies in step. Two trees a person edits is the objection above. Two trees a script writes is a build step, because nobody edits them and nobody reviews them. That is the whole argument for generating them. What the generator writes The whole opt-in is one export, at the page's normal path: app/terms-of-use/page.tsx export const isDeviceBasedSSG = true The script writes two things from it: a route per device, and the list of paths that got one. The list is a module rather than a directory scan, because the same code has to work wherever the rewrite runs, and because deriving it from the marker is what stops it drifting away from the folders it names. Each generated route carries no logic of its own: app/(generated)/desktop/terms-of-use/page.tsx // Generated. Do not edit. import Page, { metadata } from '../../../terms-of-use/page' export const dynamic = 'error' export { metadata } export default function GeneratedDeviceRoute() { return <Page device="desktop" /> } The wrapper takes no props of its own on purpose. A page file is type checked against the props Next passes a route, and a wrapper that declares anything else fails that check. Metadata is re-exported, and a wrapper that forgets it leaves the page with no title and no description, which is why the script reads the source for a metadata export and emits the re-export only when it finds one. The line that matters more than either of those is dynamic = 'error'. The one line that turns a silent failure into a build failure Nothing in a prerendered route's tree may read cookies(), headers() or searchParams. That includes the layout and the not found boundary, which sit in every route's tree. One such read anywhere puts the whole route back on per request rendering, and the build stays green while it happens. The not found page is the one that catches people out, and it is also the one page whose device genuinely cannot be known, so it takes a fixed value rather than reading for one. dynamic = 'error' is what turns that class of mistake into a build failure, and the failure names the API: `Route /desktop/terms-of-use with dynamic = "error" couldn't be rendered statically because it used headers`. The variant you serve has to be the variant the cache stored Everything above gets you two prerendered files and a rewrite that picks one. Put a shared cache in front and a new failure appears, because the cache stores one body per bucket of its own choosing, and it chose that bucket with its own classifier. Cloudflare, for one, can split a cache rule by device type and then sends the origin a CF-Device-Type header carrying the bucket it used. If middleware picks the branch with a User Agent parser while the edge keys on the CDN's answer, then for every user agent where the two disagree, whoever arrives first decides what the entire bucket serves to everyone behind them. They disagree on real traffic, and not on anything exotic. A native app's networking stack and a reading device asked to behave like a desktop both landed in one bucket at the edge and another in my parser. The particular agents will age, so take the category instead of the list: anything whose user agent does not describe the screen it draws on. Whatever you parse with has to collapse to the buckets the cache actually holds. Mine had a third answer, unknown, for anything it did not recognise, and under two buckets a third answer is not a third variant, it is a coin toss about which of the two documents a visitor gets. The read above has no third branch for that reason. Every disagreement I found became a test case. The fix is one server side read, used by every page, taking the CDN's answer first and the user agent second: lib/device.ts export function deviceFromHeaders(headers: Headers): DeviceSegment { const cdnType = headers.get('cf-device-type') ?? '' // Own keys only: outside a cache rule this header is whatever the client sent, and // `constructor` or `__proto__` would otherwise resolve to a member of the prototype. if (Object.hasOwn(LAYOUT_BY_CDN_TYPE, cdnType)) return LAYOUT_BY_CDN_TYPE[cdnType] return parseUserAgent(headers.get('user-agent') ?? '').isMobile ? 'mobile' : 'desktop' } Tablets map to the mobile layout, which means three buckets hold two documents and the tablet copy duplicates the mobile one. That is the price of letting the cache own the classification. The internal paths have to be unreachable Nothing links to /desktop/terms-of-use, so a request carrying one was typed or crawled. It is also a real route, and I checked what happens without the guard: it answers 200 and serves the document. The same page at a second URL, with whatever search weight the real one has split between them. lib/device.ts export function isInternalDevicePath(pathname: string): boolean { const [, first] = pathname.split('/') return (DEVICE_SEGMENTS as readonly string[]).includes(first) } The rewrite goes to /_not-found with an explicit status: 404. Without the status it is a soft 404: the not found page at 200, which is the duplicate the guard exists to prevent, wearing a different hat. What I would keep Keep the variant as a path segment, because nothing else reaches the key. Keep literal folders rather than an enumerated dynamic segment, because a folder cannot match a value it is not named after. Keep one device classifier for the whole application, and if a shared cache sits in front, make it the cache's classifier. Keep dynamic = 'error' on every variant route. Keep a check that reads the prerender manifest rather than the route table, and a second one that requests the URLs nobody should request and expects a 404. Keep a check on the bytes themselves, which asks a running build for the same page twice and compares what comes back: same bytes inside one device class, different bytes between classes, and no change when the request carries cookies. Take the cookie names from the application's own constants rather than retyping them, or the check sends a cookie nothing reads and quietly passes on two requests that both carried nothing. Keep the generator, and know what you are buying with it. Two hand written wrappers of four lines each do the same job today, for two cacheable pages. The day somebody marks a third page, the wrappers and the path list have to follow, and a person who forgets gets no error, just a page quietly rendering per request again. A marker that generates both of them cannot be forgotten. That is the only thing the script buys. The price is a prebuild step in front of every command a developer runs, stack traces that point at files which are not in the repository, an error that names a wrapper when the mistake is in the source page, a tree that can be stale, and a script that writes paths into code the build then compiles. For two pages it is a bad trade. For a site where anybody can mark a page, I would make it again. The generated tree is in .gitignore, and that is the call in all of this I am least settled about. It keeps the repository honest about what a person wrote. It also means nothing in the repository shows a reader what actually gets built, and a stack trace can point at a file they cannot open. The code here is a rewrite of what shipped, generic enough to drop into any project. To be sure the rewrite is more than a sketch I built a clean Next 15.5 app out of exactly these files and drove it, and the build output, the response headers and the hashes above come from that app. What a local app cannot show is how two classifiers disagree on real traffic, and that part comes from the application the design shipped in.

    1 reaction · 2 comments
  5. create-next-app でPages Routerにする選択肢を無くすらしい、ほかフレームワークを使えってことなのかな next.js#100850

    7 likes · 534 views
  6. YouTube · · YouTube description · Eddwan Hallen · vercel/eve

    Top 5 open source do GitHub | Vol. 15 #shorts Os projetos #5 a #1 do Top 15 open source do GitHub, Vol. 15, 10 segundos para cada um. #5 AIKON: emir/AIKON #4 effortless: HeyCubit/effortless #3 OpenKnowledge: inkeep/open-knowledge #2 oh-my-hermes: rlaope/oh-my-hermes #1 eve: vercel/eve Vídeo completo com os 15: youtu.be Oferecimento: Hallen Tech, hallentech.com #shorts #opensource #github

    14 likes · 360 views
  7. YouTube · · YouTube description · Eddwan Hallen · vercel/eve

    Mais 15 projetos open source do GitHub que valem o seu tempo | Vol. 15 Mais 15 projetos open source do GitHub que valem o seu tempo, em contagem regressiva e com 10 segundos para cada um: IA num Nokia de 2007, um coelho de pelúcia que conversa, uma seta gigante desenhada pelo agente, capturas de tela num varal e muito mais. 📺 Décimo quinto volume da série Open source do GitHub. Escolhidos pela análise da lista inteira (estrelas, frequência de commits, último commit, README, documentação, capturas e site), conferida em 25/10/2026. Qual desses você vai testar primeiro? Conta nos comentários! ⏱️ CAPÍTULOS E LINKS 0:00 Abertura 0:10 #15 Tendedero: cada captura de tela do Mac fica pendurada num varal acima da tela e desce quando você encosta o mouse no topo (2,5 mil estrelas) alejandrobujan/tendedero 0:20 #14 LumaBrowser: navegador com IA local: roda modelos, gera imagem, vídeo, música e voz, e deixa agentes usarem o navegador via MCP amurgola/LumaBrowser 0:30 #13 Kapitsa: motor de inferência para Apple Silicon que roda modelos locais para Claude Code, Codex, opencode e pi nace-ai/kapitsa 0:40 #12 degoog: buscador self-hosted que junta vários mecanismos de busca numa página só, com plugins e comandos bang (2,5 mil estrelas) degoog-org/degoog 0:50 #11 zoetrope: mostra uma sessão do Claude Code ou do Codex como grafo ao vivo, no terminal ou no navegador; nada sai da sua máquina (1,1 mil estrelas) furkankly/zoetrope 1:00 #10 ElatoAI: voz com IA em tempo real num ESP32 com Arduino, para criar brinquedos e aparelhos que conversam (2,1 mil estrelas) akdeb/ElatoAI 1:10 #9 OnionShare: compartilhe arquivos, hospede sites e converse de forma segura e anônima pela rede Tor (7,2 mil estrelas) onionshare/onionshare 1:20 #8 Big Arrow: ferramenta de macOS e skill para Claude Code e Codex: o agente desenha uma seta gigante para mostrar onde clicar franzenzenhofer/big-arrow-on-the-screen 1:30 #7 cronstable: agendador de tarefas em YAML ou crontab, com novas tentativas, alertas, histórico, workflows e painéis na web, no terminal e no iOS ptweezy/cronstable 1:40 #6 compose-lint: linter de segurança para Docker Compose, baseado no OWASP e no CIS Docker Benchmark, que corrige as falhas óbvias tmatens/compose-lint 1:50 #5 AIKON: Claude, OpenAI, Gemini e Grok num Nokia de 2007 (Series 40 e S60), com um pequeno servidor em Go emir/AIKON 2:00 #4 effortless: mod do Claude Code que escolhe o esforço de raciocínio de cada prompt; no teste do autor, o Opus ficou 21% mais barato que no esforço médio HeyCubit/effortless 2:10 #3 OpenKnowledge: editor de Markdown WYSIWYG local e grátis, com Claude, Codex e outros agentes editando lado a lado (4,7 mil estrelas) inkeep/open-knowledge 2:20 #2 oh-my-hermes: pacote completo para o Hermes Agent: escolhe o modelo de cada tarefa e roda o trabalho em paralelo (4,2 mil estrelas) rlaope/oh-my-hermes 2:30 Oferecimento Hallen Tech 2:44 #1 eve: framework da Vercel para agentes de IA duráveis em que a estrutura de pastas define instruções, ferramentas, skills, canais e agendamentos (5,7 mil estrelas) vercel/eve 2:54 Encerramento 🧡 Oferecimento: Hallen Tech, liderança de tecnologia sob demanda, software, cloud e segurança para a sua empresa. Site: hallentech.com E-mail: contato@hallentech.com Instagram: instagram.com LinkedIn da Hallen Tech: linkedin.com LinkedIn do Eddwan: linkedin.com Se curtiu, deixa o like, segue o canal e ativa as notificações para a próxima lista. #opensource #github #programacao #ferramentas

    142 likes · 6,263 views
  8. Next.js 16 で images.domains 廃止 — 3 アプリを remotePatterns に書き直す Cosoado Lab Blog 同時掲載予定: cosoado-lab.com Next.js 16 に上げた月曜の朝、build ログに DeprecationWarning: The "images.domains" configuration is deprecated が並んだ。v14 のドキュメントには deprecated と書いてあったが、runtime warning が出ていなかったので 2 年近く放置し続けた報いだ。Next.js 14 のリリースは 2024 年だから、計算したらそうなる。なんで今まで気づかなかったかは聞かないでほしい。SparMate・NetaPair・BoardLink の next.config.ts を一気に直した。ついでに images.localPatterns という別の罠も踏んだので両方まとめておく。 TL;DR images.domains → Next.js 16 で deprecation warning。移行先は images.remotePatterns(プロトコル・パスまで絞れる) query string 付きローカル URL <Image src="/api/photo?id=1" /> は Next.js 16 からエラー。images.localPatterns で許可が必要 参照: PR #84625 / PR #84406 images.domains が deprecated になった理由 images.domains の問題は許可の粒度が粗すぎることだ。ホスト名だけを書くと全パス・HTTP と HTTPS の両方が許可される。「このパスの HTTPS だけ許可したい」という絞り方ができない。 ?url=lh3.googleusercontent.com を渡せばそのホストからなら何でも画像最適化エンドポイントを通過してしまう。Next.js 12.3 で追加された remotePatterns はプロトコル・ホスト名・パスをそれぞれ絞れる。domains はその時点で役目を終えていた。 remotePatterns への移行 // before const config: NextConfig = { images: { domains: ['avatars.githubusercontent.com', 'lh3.googleusercontent.com'], }, } // after import type { NextConfig } from 'next' const config: NextConfig = { images: { remotePatterns: [ { protocol: 'https', hostname: 'avatars.githubusercontent.com', pathname: '/u/', }, { protocol: 'https', hostname: 'lh3.googleusercontent.com', pathname: '/a/', }, ], }, } export default config protocol: 'https' で HTTP リクエストをはじき、pathname でパスを絞る。/** は任意のサブパスにマッチするワイルドカードだ。import type { NextConfig } を書いておくと remotePatterns のプロパティが TypeScript で補完される。 1 行書き換えでは済まないが、使っているドメインを書き出すだけなので 3 アプリ分でも 15 分かからない。 localPatterns の新要件(Next.js 16 breaking change) images.domains の警告に気を取られていたら、もう 1 つ踏んだ。 Next.js 16 から、<Image> に query string 付きのローカル URL を渡すには images.localPatterns での許可が必要(PR #84406)。 // Next.js 16 から runtime error になる <Image src={/api/user-photo?userId=${userId}} width={64} height={64} alt="avatar" /> // Error: ... is using a query string which is not configured in images.localPatterns. デフォルトの localPatterns は { pathname: '/**', search: '' } — search が空文字列、つまり「query なし」しか通さない。修正: const config: NextConfig = { images: { localPatterns: [ { pathname: '/api/user-photo', // search を省略すると任意の query を許可 }, ], }, } opengraph-image.tsx + ImageResponse の OG 画像生成パターン(前の記事)は <Image> を経由しないので影響なし。admin 画面で <Image src="/api/og?preview=true" /> のように query 付きローカル URL を渡している場合は対象になる。 これは一番やった失敗です remotePatterns に移行するとき、最初に書いたのがこれだった: remotePatterns: [ { protocol: 'https', hostname: '**', // ← 全ホスト許可。domains と何も変わらない }, ], 「warning が消えた、動いた、commit」と半自動的にやった。セルフレビューで気づいたのが commit の 10 分後で、push 前だったのは不幸中の幸いだ。hostname: '**' は全ドメインを許可するワイルドカードで、images.domains: ['*'] でホワイトリスト全開放するのと同じことになる。remotePatterns の意味は「このホストのこのパスだけ通す」であって、wildcard 全開放は趣旨を外れている。 まとめ images.domains → images.remotePatterns に移行。protocol + pathname まで絞ることでセキュリティが上がる query string 付きローカル <Image> は images.localPatterns での許可が必要(Next.js 16 breaking change) hostname: '**' で移行した気になるのが一番よくないパターン 1 アプリ 10 分の作業。warning を「後でやる」で積んでいくとバージョンアップのたびに地雷が増える。 この記事の設定を実際に使っているプロダクト: SparMate — スポーツマッチングアプリ NetaPair — ネタ系交流マッチングアプリ BoardLink — ボードゲームマッチングアプリ Cosoado Lab — 個人開発ラボ

  9. YouTube · · YouTube description · Repo Radar · vercel/next.js

    next.js: The Hottest JavaScript Repo on GitHub Right Now #Shorts In this video, we break down vercel/next.js - one of the hottest open-source projects on GitHub right now. The React Framework. It has already earned 143,096 stars on GitHub and is written primarily in JavaScript. We look at what it does, why developers are paying attention to it right now, and who should try it. 📂 Repo: vercel/next.js 🌐 Stars: 143,096 | Language: JavaScript | Forks: 35,275 🔔 Subscribe for daily picks of the hottest GitHub repos: / @reporadar #github #opensource #programming keywords: coding, developer tools, github, opensource, programming, software, tech #Shorts

    20 views
  10. eslint-plugin-reactから@eslint-react/eslint-pluginに移行 Reactのlintルールをeslint-plugin-reactから@eslint-react/eslint-pluginに移行した。 移行の理由 eslint-plugin-reactがESLint 10へのブロッカーになっていたため。ESLint 10では非推奨のAPIの削除があったが、eslint-plugin-reactでは未対応であった。ESLint 10への対応PR自体は7月末出ていたが、記事執筆時点(2026/10/24)では未マージである。 PR: eslint-plugin-react#4022 Next.jsのプロジェクトでは、プロジェクト側で互換レイヤーを作成することによって使用を継続していた。ただ、AIである程度簡単にできるとはいえESLintの変更に応じてパッチが必要になるのも面倒なので移行した。 Next.jsの変更: github.com/vercel/next.js/commit/907721a66e8b4174… 移行先 @eslint-react/eslint-pluginのリポジトリ: Rel1cx/eslint-react ルールについては全てのeslint-plugin-reactのルールをカバーしているわけではないので、移行の際には注意が必要である。 ルールの対応表: eslint-react.xyz 個人のリポジトリであるため、会社のルールによっては使えない可能性もある。ESLint Communityへの招待も来ているが、実際に今後移管されるかは未定である。 招待: eslint-react#1950

Trending awards, every completed day and week
Gold
121st place finishes
Silver
132nd place finishes
Bronze
63rd place finishes
Awarded repositories
7Owned by vercel now

Repositories

Ordered by gold, then silver, then bronze.

  1. pkgBest: 1st by stars, 1,075 stars310
  2. swrBest: 1st by stars, 3,179 stars301
  3. turborepoBest: 1st by stars, 2,057 stars253
  4. next.jsBest: 1st by stars, 4,506 stars231
  5. satoriBest: 1st by stars, 1,818 stars220
  6. commerceBest: 2nd by stars, 456 stars020
  7. chatbotBest: 3rd by stars, 986 stars001

A repository earns an award for finishing first, second or third in a completed trending day or week, ranked by new stars, new forks or pull request authors. Awards follow a repository through renames and transfers and count toward its current owner. Weeks run Monday to Sunday, UTC.