惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

S
Schneier on Security
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
T
Threat Research - Cisco Blogs
C
Cyber Attacks, Cyber Crime and Cyber Security
C
CXSECURITY Database RSS Feed - CXSecurity.com
A
Arctic Wolf
Security Latest
Security Latest
Simon Willison's Weblog
Simon Willison's Weblog
I
Intezer
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
T
Troy Hunt's Blog
Latest news
Latest news
Help Net Security
Help Net Security
S
Security Affairs
Webroot Blog
Webroot Blog
The Hacker News
The Hacker News
AI
AI
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
T
Tor Project blog
Forbes - Security
Forbes - Security
Google DeepMind News
Google DeepMind News
AWS News Blog
AWS News Blog
Attack and Defense Labs
Attack and Defense Labs
P
Proofpoint News Feed
www.infosecurity-magazine.com
www.infosecurity-magazine.com
H
Help Net Security
L
Lohrmann on Cybersecurity
S
SegmentFault 最新的问题
Google Online Security Blog
Google Online Security Blog
MongoDB | Blog
MongoDB | Blog
Cyberwarzone
Cyberwarzone
The Last Watchdog
The Last Watchdog
S
Securelist
N
News and Events Feed by Topic
S
Secure Thoughts
F
Fortinet All Blogs
博客园_首页
C
Cybersecurity and Infrastructure Security Agency CISA
量子位
M
MIT News - Artificial intelligence
F
Full Disclosure
T
The Blog of Author Tim Ferriss
T
Tailwind CSS Blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Microsoft Security Blog
Microsoft Security Blog
I
InfoQ
P
Privacy International News Feed
L
LangChain Blog
Know Your Adversary
Know Your Adversary
C
CERT Recently Published Vulnerability Notes

Vercel News

Vercel Open Source Program: Winter 2026 cohort How Notion Workers run untrusted code at scale with Vercel Sandbox How we run Vercel's CDN in front of Discourse From idea to secure checkout in minutes with Stripe Building Slack agents can be easy Scaling redirects to infinity on Vercel Advancing Python typing Gamma builds design-first agents with Vercel How Avalara turns pipe dreams into patent-pending with v0 Keeping community human while scaling with agents How OpenEvidence built a healthcare AI that physicians actually trust Security boundaries in agentic architectures Skills Night: 69,000+ ways agents are getting smarter Video Generation with AI Gateway We Ralph Wiggumed WebStreams to make them 10x faster How Stably ships AI testing agents in hours, not weeks How we built AEO tracking for coding agents Anyone can build agents, but it takes a platform to run them Introducing Geist Pixel The Vercel AI Accelerator is back with $6m in credits Making agent-friendly pages with content negotiation The Vercel OSS Bug Bounty program is now available Introducing the new v0 Run untrusted code with Vercel Sandbox, now generally available How Stripe built a game-changing app in a single flight with v0 How Sensay went from zero to product in six weeks AGENTS.md outperforms skills in our agent evals Agent skills explained: An FAQ Testing if "bash is all you need" AWS databases are now live on the Vercel Marketplace and v0 Use Perplexity Web Search with Vercel AI Gateway Introducing: React Best Practices Nick Bogaty joins Vercel as Chief Revenue Officer How Mux shipped durable video workflows with their @mux/ai SDK How to build agents with filesystems and bash How we made v0 an effective coding agent Stopping the slow death of internal tools Building AI-Generated Pixel Trading Cards with Vercel AI Gateway We removed 80% of our agent’s tools AI SDK 6 Our $1 million hacker challenge for React2Shell Cline now runs on Vercel AI Gateway How to prompt v0 Build smarter workflows with Notion and v0 Vercel launches partner certification Inside Workflow DevKit: How framework integrations work React2Shell Security Bulletin | Vercel Knowledge Base Billions of requests: Black Friday-Cyber Monday 2025 Investing in the Python ecosystem AWS Databases coming to the Vercel Marketplace How we built the v0 iOS app Workflow Builder: Build your own workflow automation platform Vercel Open Source Program: Fall 2025 cohort Self-driving infrastructure Vercel collaborates with Google for Gemini 3 Pro Preview launch Vercel: The anti-vendor-lock-in cloud How Nous Research used BotID to block automated abuse at scale How AI Gateway runs on Fluid compute What we learned building agents at Vercel Build and deploy data applications on Snowflake with v0 BotID Deep Analysis catches a sophisticated bot network in real-time Vercel achieves TISAX AL2 compliance to serve automotive partners Bun runtime on Vercel Functions David Totten Joins Vercel to Lead Global Field Engineering Vercel Ship AI 2025 recap You can just ship agents AI agents and services on the Vercel Marketplace Built-in durability: Introducing Workflow Development Kit Zero-config backends on Vercel AI Cloud Introducing Vercel Agent: Your new Vercel teammate Update regarding Vercel service disruption on October 20, 2025 Agents at work, a partnership with Salesforce and Slack Running Next.js in ChatGPT: How to Build ChatGPT Apps Talha Tariq joins Vercel as CTO of Security Just another (Black) Friday Server rendering benchmarks: Fluid Compute and Cloudflare Workers Towards the AI Cloud: Our Series F Collaborating with Anthropic on Claude Sonnet 4.5 to power intelligent coding agents Preventing the stampede: Request collapsing in the Vercel CDN BotID uncovers hidden SEO poisoning How we made global routing faster with Bloom filters What you need to know about vibe coding Scale to one: How Fluid solves cold starts Addressing security & quality issues with MCP tools - Vercel AI agents at scale: Rox’s Vercel-powered revenue operating system Agentic Infrastructure Zero Data Retention on AI Gateway Optimizing Vercel Sandbox snapshots How Waldium made a blog platform work for humans and AI alike How FLORA shipped a creative agent on Vercel's AI stack Agent responsibly Making Turborepo 96% faster with agents, sandboxes, and humans Unified reporting for all AI Gateway usage new.website joins forces with v0 SERHANT.'s playbook for rapid AI iteration Two startups at global scale without DevOps Chat SDK brings agents to your users 360 billion tokens, 3 million customers, 6 engineers Meet the 2026 Vercel AI Accelerator Cohort Build knowledge agents without embeddings
Migrating Grep from Create React App to Next.js - Vercel – Vercel
2025-04-14 · via Vercel News

6 min read

Grep is extremely fast code search. You can search over a million repositories for specific code snippets, files, or paths. Search results need to appear instantly without loading spinners.

Originally built with Create React App (CRA) as a fully client-rendered Single-Page App (SPA), Grep was fast—but with CRA now deprecated, we wanted to update the codebase to make it even faster and easier to maintain going forward.

Here's how we migrated Grep to Next.js—keeping the interactivity of a SPA, but with the performance improvements from React Server Components.

Link to headingWhy Next.js and React Server Components?

Traditional SPAs can navigate between pages quickly. However, this comes at the expense of a larger client-side JavaScript bundle needed to be fetched and parsed before the page is interactive.

We wanted the fastest possible initial page load, while also keeping quick navigations between subsequent pages. Next.js can prerender the majority of the page for predictable and fast initial loads. Then, it can intelligently prefetch other pages, keeping the quick navigation feel of a SPA.

Let's talk about how we migrated to Next.js, while also making the search input interactive faster and getting rid of unnecessary layout shift in the process.

Link to headingPreserving an instant search experience

Grep needs the fastest possible search. Start typing on the homepage, transition to /search, and keep typing—all without losing focus. Notice how in the video, the search moves from the center of the home page to the navbar on /search.

With a traditional SPA, the search state stays on the client and the UI remains intact. But when moving state to the server, we needed to keep the search input state consistent across page navigations.

Link to headingRoot layout and repositioning

You could place the search bar in Next.js’ root layout. This would prerender the search bar in one place and then you could quickly reposition it on the client after hydration.

layout.tsx

export default function RootLayout({ children }) {

return (

<>

<script dangerouslySetInnerHTML={{ __html: "..repositioning logic.." }} />

<SearchBar />

<main>{children}</main>

</>

);

}

However, this will cause hydration mismatches and cause layout shift. Further, we need to synchronize the state to the URL through search params. Reading searchParams from the incoming request in the root layout will limit our ability to prerender the majority of the page.

Link to headingConditional rendering and shared state

Rather than repositioning the element, we could have two references to the search input and conditionally render them when needed.

  1. Homepage (client-rendered): Center the search bar for a clean, Google-like experience

  2. Header (server-rendered): Display the search bar for all routes except the homepage

This keeps the homepage minimal while ensuring the search bar instantly appears in the top nav when navigating away—no layout shifts, no flickers.

Here’s how it works. Our header contains the search bar, but only for the /search route.

components/header.tsx

"use client";

export function Header() {

const pathname = usePathname();

return (

<header>

<Logo />

{pathname !== '/' && <SearchBar />}

<ThemeToggle />

</header>

);

}

The header is rendered in the root layout, keeping it server-rendered across pages:

layout.tsx

import { Header } from '@/components/header';

export default function RootLayout({ children }) {

return (

<html>

<body>

<Header />

{children}

</body>

</html>

);

}

Meanwhile, the homepage renders its own version of the SearchBar component:

page.tsx

export function Homepage() {

return (

<div className="search-centered">

<SearchBar />

</div>

);

}

This allows the user to start typing on the home page and not lose focus on the input (even though the position shifts) when transitioning to the next page.

Link to headingKeeping the search state in sync

If we're rendering two search inputs, we need to keep their state in sync. The key is URL-based state combined with lightweight shared state management.

  1. Homepage: <SearchBar> updates query string, navigating to /search?q=...

  2. Navbar: <SearchBar> reads from URL query parameters, synchronizing on mount

To provide more responsive local feedback, especially during rapid typing, we also manage local input state through React Context and hooks.

Link to headingServer-first, client-second data fetching

Now, we need to instantly display the search results. The challenge is the handoff between the server and client. Going back to the server for every keystroke could be slow, and doing all data fetching on the client means a slower initial loading experience.

Luckily, there's great solutions in the React community for data fetching like SWR and TanStack Query. These abstractions allow you to prefetch data on the server and then hydrate the client-side cache.

Link to headingServer-side initial fetch and hydration

When you first visit the search results page (e.g., /search?q=react), the server starts fetching data immediately—without blocking. HTML begins streaming to the client while the query is still in-flight.

This is possible because React Server Components can serialize Promises and pass them through a HydrationBoundary, allowing TanStack Query to resume the query on the client without redundant fetches.

search/page.tsx

import { /* ... */} from "@tanstack/react-query";

import { ResultsClient } from "@/components/results";

import { apiSearch } from "@/lib/api";

import { getFiltersFromRawSearchParams } from "@/lib/utils";

const queryClient = new QueryClient({

defaultOptions: {

dehydrate: {

// Include pending queries so the client can pick them up

shouldDehydrateQuery: (query) =>

defaultShouldDehydrateQuery(query) || query.state.status === 'pending'

}

}

});

const searchOptions = (filters) => ({

queryKey: ["search", filters],

queryFn: () => apiSearch(filters),

});

export default function SearchPage({ searchParams }) {

const filters = getFiltersFromRawSearchParams(searchParams);

// Kick off the query on the server without 'await'

queryClient.prefetchQuery(searchOptions(filters));

return (

// Pass promise to client

<HydrationBoundary state={dehydrate(queryClient)}>

<ResultsClient />

</HydrationBoundary>

);

}

On the client, useSuspenseQuery hydrates from the server-initiated request, resolving as the data streams in. Once this initial data from the server is loaded, all further searches happen on the client to avoid unnecessary server round trips.

components/results.tsx

"use client";

import { useSuspenseQuery } from "@tanstack/react-query";

import { searchOptions } from "@/lib/queries";

export function ResultsClient() {

const filters = useFilters(); // client-side filter + input state

const { data } = useSuspenseQuery(searchOptions(filters));

return <Hits data={data} />;

}

Link to headingIncremental client-side fetching with TanStack Query

After the initial server-side load, subsequent fetches—triggered by typing, filtering, or pagination—happen client-side with TanStack Query’s useSuspenseQuery. To avoid excessive requests, we debounce input changes using React’s useDeferredValue:

components/results.tsx

'use client';

import { useSuspenseQuery } from '@tanstack/react-query';

import { useDeferredValue, Suspense } from 'react';

import { searchQueryOptions } from '@/lib/queries';

import { useNonOptimisticFilters } from '@/hooks/filters';

import { Hits } from '@/components/hits';

function ResultsInner({ filters }) {

const { data } = useSuspenseQuery(searchQueryOptions(filters));

return <Hits data={data} />;

}

export function ResultsClient() {

const filters = useNonOptimisticFilters();

const deferredFilters = useDeferredValue(filters);

return (

<Suspense fallback={<ResultsSkeleton />}>

<ResultsInner filters={deferredFilters} />

</Suspense>

);

}

This ensures new results load smoothly while preventing unnecessary network calls during input changes.

Link to headingPreventing stale or out-of-order results

Rapid typing introduced another challenge: older network responses sometimes arrive after newer ones, briefly displaying outdated results. For example, typing "foo", then deleting back to "f", could cause "foo" results to reappear after "f" was already rendered.

To prevent this, we combined two key strategies:

  1. Optimistic UI updates with useOptimistic: React’s useOptimistic hook handles optimistic state across async updates, ensuring that the UI always reflects the latest user input—regardless of network latency

  2. Query caching with TanStack Query keys: Using unique cache keys (["search", filters]), TanStack Query ensures only the latest request updates the UI, discarding stale responses

Here’s how we implemented useOptimistic alongside React’s useTransition for instant, reliable feedback:

hooks/filters.ts

"use client";

import { useRouter, usePathname, useSearchParams } from "next/navigation";

import { useOptimistic, useTransition } from "react";

export function useFilters() {

const currentFilters = /* ... */;

const [optimisticFilters, setOptimisticFilters] = useOptimistic(

currentFilters,

(_, updatedFilters) => updatedFilters

);

const [isPending, startTransition] = useTransition();

const updateFilters = (updateFn) => {

const newFilters = updateFn(currentFilters);

setOptimisticFilters(newFilters);

// ...

startTransition(() => {

router.replace("/search?" + params.toString());

});

};

return { filters: optimisticFilters, updateFilters, isPending };

}

This eliminated stale or flickering search results during rapid input changes.

Link to headingPrefetching dynamic search routes

One last optimization was necessary for instantaneous page transitions. Next.js automatically prefetches static routes, but not dynamic ones like /search?q=react. To make search results feel instant, we explicitly prefetch the shared layout:

components/prefetch-search-layout.tsx

"use client";

import { useEffect } from "react";

import { useRouter } from "next/navigation";

export function PrefetchSearchLayout() {

const router = useRouter();

useEffect(() => {

router.prefetch("/search?q=");

}, [router]);

return null;

}

This component is rendered alongside the search bar on the homepage. As users start typing and navigate to /search, the layout is already cached—making the transition feel instant, even before the query results resolve.

Link to headingSolving mobile-specific challenges

There was one layout challenge left with mobile Safari. Focusing the search input caused unpredictable scroll and zoom behaviors, breaking the layout. To stabilize the input, we introduced a usePreventScroll hook:

usePreventScroll.tsx

"use client";

import { useEffect } from "react";

export function usePreventScroll(isFocused: boolean) {

useEffect(() => {

document.body.style.overflow = isFocused ? "hidden" : "";

}, [isFocused]);

}

By adding this hook to the home page client component, it locks the page overflow, preventing layout jumps and keeping the search experience stable on mobile.

Link to headingAdditional performance gains with Partial Prerendering

To push Grep’s performance further, we enabled Next.js’s new experimental Partial Prerendering (PPR). PPR blends static prerendering with dynamic streaming, delivering fast initial loads without refactoring.

Before PPR, search bar interactivity was briefly delayed by client-side hydration. With PPR enabled, the search experience became nearly instantaneous:

  • Static shell: Core UI loads instantly, providing immediate visual feedback

  • Dynamic streaming: Search results stream in milliseconds later, keeping the page interactive without delay

By enabling PPR, we combined static prerendering with dynamic streaming—with no additional code changes. The first-load experience became instant, with search interactivity arriving only milliseconds later. No refactoring. No tradeoffs. Just faster page loads with a single flag.

Link to headingFinal results and what’s next for Grep

Migrating to Next.js transformed Grep into a fast, search-as-you-type experience—reducing client-side execution while maintaining SPA-level responsiveness.

  • Instant, persistent input: The search bar remains focused across pages with no flickers or layout shifts

  • Optimized data fetching: Server-side prefetching and incremental client-side updates ensure fast, real-time search

  • Improved performance: Fewer blocking scripts and targeted hydration reduce bundle sizes and speed up rendering

  • Enhanced experience: While we were at it, we shipped dark mode and expanded our search index from 500,000 to 1,000,000 GitHub repos

We also achieved measurable performance gains by enabling PPR, leading to faster initial loads by combining static UI with streamed dynamic content. Combined with broader improvements from migrating to Next.js, the result was a faster, more stable experience—especially on mobile.

Metric

CRA (Mobile)

Next.js (Mobile)

Results

First Contentful Paint (FCP)

3.0 seconds

0.9 seconds

70% faster

Largest Contentful Paint (LCP)

3.7 seconds

3.2 seconds

13.51% faster

Speed Index

4.7 seconds

2.9 seconds

38.3% faster

Network Request Finish

1.49 seconds

0.4 seconds

73.15% faster

The results show good improvement due to smarter, more flexible rendering strategies and the ability to fetch data on the server. With this new, stable foundation established, we’re already exploring further improvements:

  • Private repository indexing: Secure authentication for private code search, using serverless functions and Edge Middleware for security

  • Advanced query syntax: Support for GitHub-style filters like repo:vercel/next.js or language:typescript

The move to Next.js wasn’t just about performance—it was about setting Grep up for the future. Faster queries, smoother interactions, and a stronger foundation for what’s next.

Try Grep for yourself.