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— whenpercentageis 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:
"
onclickis the most primitive DOM Level 0 event listener.addEventListeneris 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, soonClickhas 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.gpuisundefinedwhen unsupported.!!negates again, guaranteed to convert totrue | 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.
Top 2 from juejin.cn, machine-translated. The original thread is authoritative.
[ThumbsUp][ThumbsUp]
I thought it was a tutorial from the title [Facepalm]