跪拜 Guibai
← Back to the summary

DeepSeek-R1 Runs Locally in the Browser with WebGPU and React

Written in front: Today I learned something that "blew my mind" — on-device models. Before, when we used AI, we had to call remote APIs, send data out, wait forever, and worry about privacy leaks. The teacher said that now small-parameter models can run right in the browser! Yes, you read that right — running an LLM in the browser. We built a React + TypeScript + TailwindCSS project by hand, loading DeepSeek-R1's 1.5B distilled model in the browser using WebGPU. The moment I saw the model finish loading locally and the chat box ready for typing, I was thrilled — from now on, no more sending data to third parties!


1. On-Device Models: AI Goes "Local"

1.1 The Pain Points of Remote API Calls

The teacher said:

"Unlike OpenAI/DeepSeek API calls — where the LLM is remote and not co-located with the calling client. Expensive, insecure. Context gets sent to the server with every request."

Three major problems with remote calls:

Problem Explanation Analogy
Expensive A few bucks per million tokens; frequent calls hurt your wallet Taking a taxi for every meal instead of eating at the diner downstairs
Insecure Your prompts and data get sent to someone else's server Mailing your diary to a stranger to have them read it for you
Network-dependent Can't use it without internet Turns into a brick when offline

1.2 The Benefits of On-Device Models

The teacher said:

"Ollama local open-source model deployment, on the client side — on-device models. Mobile, automotive, agent-side. Open-source small-parameter models can handle these tasks. In the browser, download anytime, use anytime. WebGPU."

On-device model = the model runs locally on the user's machine, no internet, data never leaves.

Our project uses DeepSeek-R1-Distill-Qwen-1.5B — a 1.5-billion-parameter distilled reasoning model, specifically optimized to run in the browser with WebGPU acceleration.


2. React + TypeScript + TailwindCSS: The "Infrastructure" of the AI Era

2.1 Why This Tech Stack?

The teacher said:

"React + TS — the go-to frontend tech for large-scale projects in the AI era. React has a steeper learning curve than Vue, but it's used more in large projects, and AI training code tends to favor React."

TailwindCSS has even become the standard for Vibe Coding:

The teacher said:

"You almost never need to write CSS anymore — utility-first CSS. TailwindCSS has become the basic building block of Vibe UI. Just write class names, natural semantic programming."

Writing CSS before:

.container {
    display: flex;
    flex-direction: column;
    align-items: center;
    height: 100vh;
    background-color: white;
}

With TailwindCSS:

<div className="flex flex-col items-center h-screen bg-white">

This is "natural semantic programming" — describing styles with English words right in JSX, no more writing selectors and CSS rules.

2.2 React Components: Functions Are Components

"Build pages like building with blocks. React encapsulates a component — a function is a component. A function returning HTML is a component."

Progress component — a progress bar:

function formatBytes(size) {
    const i = size == 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024));
    return (
        +(size / Math.pow(1024, i)).toFixed(2) * 1 +
        ["B", "kB", "MB", "GB", "TB"][i]
    );
}

const Progress = ({ text, percentage, total }) => {
    percentage ??= 0;  // ES12 nullish coalescing assignment
    return (
        <div className="w-full bg-gray-100 text-left rounded-lg overflow-hidden mb-0.5">
            <div
                style={{ width: `${percentage}%` }}
                className="bg-blue-400 whitespace-nowrap px-1"
            >
                {text}
                {percentage.toFixed(2)}%
                {isNaN(total) ? "" : `of ${formatBytes(total)}`}
            </div>
        </div>
    );
};

export default Progress;

A few key points:

Concept Explanation Code
Props Attributes passed from a parent component; child components only display, never modify { text, percentage, total }
Nullish coalescing ES12 syntax, defaults to 0 when no argument is passed percentage ??= 0
JSX Writing HTML inside JavaScript <div className="...">{text}</div>
className Because class is a reserved keyword in JS, className is used instead className="w-full bg-gray-100"

The teacher said:

"The encapsulator thinks more so the user has a smoother experience. percentage ??= 0 — when percentage is nullish, assign it 0. During initialization, there's no concept of download progress. What if a value is passed? The passed-in value is not overwritten."


3. React's Two Kinds of Data: State and Props

3.1 State: A Component's Own State

Looking at state management in App.tsx:

const [input, setInput] = useState('');        // Input box content
const [status, setStatus] = useState(null);    // Model status null | loading | ready
const [error, setError] = useState(null);       // Error message
const [loadingMessage, setLoadingMessage] = useState('Starting load...');
const [progressItems, setProgressItems] = useState([]);  // Download progress list

The teacher emphasized a particular design mindset:

"Data state drives the interface state. Data has different states, the interface has different states — like Sichuan opera face-changing."

Value of status Interface behavior
null Show the "Load Model" button
"loading" Show download progress bar, button disabled
"ready" Show the chat input box

This "state-driven" thinking makes UI logic extremely clear.

3.2 Props: Passed from Parent to Child Component

// Parent component App renders Progress in a loop
progressItems.map(({ text, percentage, total }, i) => (
    <Progress
        key={i}
        text={text}
        percentage={percentage}
        total={total}
    />
))

The teacher said:

"Props are attributes passed from a parent component to a child component and cannot be modified inside the child. The child component is mainly responsible for display — whatever Props the parent gives me, I display it that way. To modify, you must report back to the parent."

State = a component's own "private savings." Props = "allowance" from the parent, can only be spent, not changed.


4. The Difference Between React Synthetic Events and Native Events

4.1 The History of DOM Events

Looking at event.html:

<button id="btn" onclick="console.log('Button clicked')">Button</button>

<script>
    document.getElementById('btn')
        .addEventListener('click', function() {
            console.log('Button clicked')
        })
</script>

The teacher said:

"onclick is the most primitive DOM Level 0 event listener. addEventListener is a DOM Level 2 event listener — the same DOM element can listen to the same event multiple times."

4.2 React's Synthetic Events

"React has a code neatness obsession — it avoids inventing new concepts if possible. @ event binding is Vue's approach. React uses existing concepts directly, so onClick has zero learning curve for experienced devs."

Vue:

<button @click="handleClick">Click me</button>

React:

<button onClick={() => { setStatus("loading"); }}>Click me</button>

The teacher said:

"Events in React are not native events; they are SyntheticEvents. They wrap the browser's native events to solve cross-browser compatibility issues."

Using onClick instead of @click, className instead of class — React's principle is "avoid inventing new concepts whenever possible."


5. Detecting WebGPU + State-Driven UI

const IS_WEBGPU_AVALABLE = !!navigator.gpu;

"! negates — navigator.gpu is undefined when unsupported. !! negates again, guaranteed to convert to true | false. A double negative equals a positive."

Then the entire App's return splits into two paths based on this value:

return IS_WEBGPU_AVALABLE ? (
    // WebGPU supported — show the full LLM interface
    <div className="flex flex-col h-screen mx-auto items-center ...">
        {/* Title, button, progress bar, chat box */}
    </div>
) : (
    // Not supported — prompt the user
    <div>Your browser does not support WebGPU yet</div>
);

This ternary expression is "conditional rendering" — rendering different UIs based on different conditions.


6. Summary: On-Device AI is the Future

Concept Explanation
On-device model Running an LLM locally on the client, no dependency on remote APIs
WebGPU Browser-side GPU acceleration API, supports local model inference
React component A function returning JSX, building pages component by component
JSX JavaScript + XML, writing HTML inside JavaScript
State A component's own state, managed by useState
Props Attributes passed from parent to child component, read-only
Synthetic events React's cross-browser event system wrapper
TailwindCSS Utility-first CSS, writing styles with natural semantics
Nullish coalescing ??= ES12 syntax, assigns a value only when the variable is nullish

Advantages of on-device models: no cost for API calls, data stays local, works offline. React + TypeScript + TailwindCSS is the standard infrastructure for frontend in the AI era; this tech stack lets you rapidly build interfaces for AI applications.


Written at the End

The biggest takeaway today was watching an LLM run right in the browser with my own eyes. I used to think "running AI locally" was something only algorithm engineers did; now I realize frontend devs can do it too — using WebGPU + Transformers.js + ONNX Runtime, a frontend engineer can make AI run "in-place" right in the user's browser. That feeling is incredibly cool.

Next time an interviewer asks you: "What's the difference between on-device models and API calls? What are React's synthetic events?"

You can calmly say:

"On-device models run LLMs locally on the client, for example, loading DeepSeek-R1's distilled model in the browser via WebGPU. Compared to remote API calls, the advantages are that it's free, data-secure (stays local), and works offline. React's synthetic events (SyntheticEvent) are a cross-browser wrapper around native browser events, solving compatibility issues across different browser event models. React uses camelCase names like onClick for event binding, rather than Vue's @click — the basic principle is to avoid inventing new concepts whenever possible. In the project, we also used TailwindCSS's utility classes for styling, no handwritten CSS needed, just describe styles with natural semantics right in className."

Then watch the interviewer's satisfied expression, and silently think to yourself: Nailed it, once again.


All code examples in this article come from classroom learning materials and are genuinely runnable.

Comments

Top 2 from juejin.cn, machine-translated. The original thread is authoritative.

Darling噜啦啦

[ThumbsUp][ThumbsUp]

bug小李子

I thought it was a tutorial from the title [Facepalm]