use-local-llm: 실제 로컬에서 동작하는 AI를 위한 React Hooks
Source: Dev.to

드디어 로컬 LLM을 실행시켰습니다. 모델을 받아 curl 로 테스트해 보니 아름답게 동작했습니다. 그런데 이 모델을 React 앱에 통합하려고 하는 순간, 벽에 부딪히게 됩니다.
모두가 사용하는 도구들은 서버에서 OpenAI나 Anthropic을 호출한다는 전제를 가지고 있습니다. 브라우저에서 직접 localhost:11434 로 통신한다는 상황을 기대하지 않으며, 만약 가능하다고 해도 API 라우트를 만들고 백엔드를 추가하도록 강요해 프로토타입을 복잡하게 만듭니다.
이러한 좌절감에 지쳐 저는 use-local-llm 을 만들었습니다 — 로컬 모델에서 AI 응답을 브라우저에서 바로 스트리밍하는 한 가지 일을 탁월하게 수행하는 라이브러리입니다. 백엔드가 전혀 필요 없습니다.
왜 기존 도구들이 맞지 않을까
Vercel AI SDK를 바로 사용할 수 있을 것이라고 생각할 수 있습니다. 이것은 React + AI의 표준이며, 설계가 잘 되어 있고, 문서도 충실하며, 실전에서 검증되었습니다.
하지만 이것은 여러분이 하려는 일에 맞게 만들어진 것이 아닙니다.
Vercel AI SDK는 API 레이어가 필요합니다. 여러분의 React 앱이 POST 요청을 Next.js 서버에 보내고, 그 서버가 LLM을 호출한 뒤 스트리밍으로 결과를 반환합니다. 이는 OpenAI나 Anthropic을 사용하는 프로덕션 앱에서는 타당합니다—인증, 비용 추적, 보안을 위해 백엔드가 필요하기 때문이죠.
LLM이 이미 localhost:11434에서 로컬로 실행 중이라면, 그 서버는 오히려 마찰만 늘립니다: 작성해야 할 코드가 늘고, 지연 시간이 증가하며, 빠른 프로토타입을 만들 때 복잡성이 증가합니다.
제가 원했던 것은 간단했습니다: 로컬 모델과 직접 통신하는 단일 React 훅. 중간 매개자가 없습니다. API 라우트도 없습니다. 오직 React와 실행 중인 LLM만 있으면 됩니다.
Browser → fetch() → localhost:11434 → streaming tokens back이것이 전체 아키텍처이며, 중간 단계가 전혀 없습니다.
작동 방식
use-local-llm은(는) 로컬 LLM으로부터 브라우저에서 직접 응답을 스트리밍하는 React 훅을 제공합니다. API 라우트가 없습니다. 백엔드도 없습니다. 하나의 훅과 실행 중인 모델만 있으면 됩니다.
하나의 훅. 그게 전부입니다.
function Chat() {
const { messages, send, isStreaming } = useOllama("gemma3:1b");
return (
<>
{messages.map((m, i) => (
<p key={i}>
<strong>{m.role}:</strong> {m.content}
</p>
))}
<button onClick={() => send("Hello!")} disabled={isStreaming}>
{isStreaming ? "Generating…" : "Send"}
</button>
</>
);
}이것은 완전한 스트리밍 채팅 인터페이스입니다. 메시지 기록? 처리됩니다. 스트리밍 상태? 처리됩니다. 생성 중간에 중지? 처리됩니다. 모든 복잡성이 훅 내부에 감싸져 있습니다.
/api/chat 라우트가 없습니다. Next.js도 없습니다. 백엔드 설정도 없습니다. React가 가장 잘하는 일을 하는 것뿐입니다.
Any Local LLM과 함께 작동
Ollama, LM Studio, llama.cpp 또는 OpenAI와 호환되는 서버를 사용하든, 그대로 작동합니다:
const ollama = useLocalLLM({ endpoint: "http://localhost:11434", model: "gemma3:1b" });
const lmStudio = useLocalLLM({ endpoint: "http://localhost:1234", model: "local-model" });
const llamaCpp = useLocalLLM({ endpoint: "http://localhost:8080", model: "model" });백엔드는 포트 번호를 기준으로 자동 감지됩니다. 각 도구는 자체 스트리밍 프로토콜을 사용하므로, 어떤 툴을 선택하든 최적의 성능을 얻을 수 있습니다.
고급: 토큰‑별 제어
세밀한 제어가 필요하신가요? 스트리밍되는 각 토큰에 연결하세요:
function Writer() {
const { output, generate, isStreaming } = useStreamCompletion({
endpoint: "http://localhost:11434",
model: "gemma3:1b",
onToken: (token) => console.log("Token:", token),
});
return (
<>
<pre>{output}</pre>
<button onClick={() => generate("Write a haiku about React")}>
Generate
</button>
</>
);
}사용 가능한 모델 찾기
사용자가 수동 설정 없이 사용 가능한 모델을 선택하도록 하세요:
function ModelPicker() {
const { models, isLoading } = useModelList();
return (
<select disabled={isLoading}>
{models.map((m) => (
<option key={m.name} value={m.name}>
{m.name}
</option>
))}
</select>
);
}How It Compares
| Feature | Vercel AI SDK | use‑local‑llm |
|---|---|---|
| Target | Cloud LLMs (OpenAI, Anthropic…) | Local LLMs (Ollama, LM Studio, llama.cpp) |
| Architecture | Client → Server → Cloud API | Client → Local LLM directly |
| Backend needed | Yes | No |
| Setup time | 10 + minutes | ~2 minutes |
| Bundle size | ~50 KB+ | 2.8 KB (gzipped) |
| Dependencies | Multiple | Zero (React only) |
| Privacy | Data leaves your machine | Never leaves your machine |
사용 사례에 맞는 도구를 선택하세요. 프로덕션 환경에서 OpenAI를 호출한다면 Vercel AI SDK를 사용하고, 로컬에서 프로토타이핑하거나 프라이버시를 우선시한다면 use‑local‑llm을 사용하세요.
Source: …
왜 이렇게 설계되었을까
의존성 0개 (전체 2.8 KB)
전체 패키지는 gzipped 기준 2.8 KB입니다. 런타임 의존성이 없으며, React만 피어로 사용합니다.
왜 중요한가요? 프로토타입을 즉시 설치할 수 있고 프로젝트 내 다른 패키지와 절대 충돌하지 않기 때문입니다. 의존성 충돌도, 버전 불일치도, 불필요한 용량 증가도 없습니다.
React 외부에서도 동작
핵심 스트리밍 함수(streamChat()와 streamGenerate())는 async generator이며 React, Vue, 순수 JS, Node.js 스크립트 어디서든 사용할 수 있습니다. React 앱에서는 훅을, 그 외 환경에서는 제너레이터를 직접 사용하면 됩니다.
const stream = streamChat({
endpoint: "http://localhost:11434",
model: "gemma3:1b",
messages: [{ role: "user", content: "Hello" }],
});
for await (const chunk of stream) {
process.stdout.write(chunk.content);
}AbortController 통합
모든 스트림은 즉시 취소할 수 있습니다. 사용자가 중단한 경우에도 오류 상태가 발생하지 않는데, 이는 생성 중단이 정상적인 사용자 행동이며 오류가 아니기 때문입니다.
완전한 TypeScript 지원
모든 것이 강력히 타입 지정됩니다. IDE 자동 완성, 타입 안전성, 각 함수에 대한 명확한 계약을 얻을 수 있습니다:
import type {
Backend, // "ollama" | "lmstudio" | "llamacpp" | "openai-compatible"
ChatMessage, // { role: "system" | "user" | "assistant", content: string }
StreamChunk, // { content: string, done: boolean, model?: string }
LocalModel, // { name, size?, modifiedAt?, digest? }
} from "use-local-llm";아키텍처
┌─────────────────────────────────────────────────┐
│ Your React App │
│ │
│ useOllama("gemma3:1b") │
│ │ │
│ ▼ │
│ useLocalLLM({ endpoint, model, ... }) │
│ │ │
│ ▼ │
│ streamChat() / streamGenerate() │
│ │ async generators │
│ ▼ │
│ parseStreamChunk() │
│ │ NDJSON + SSE parser │
│ ▼ │
│ fetch() + ReadableStream │
└─────────┬───────────────────────────────────────┘
│ HTTP (no server in between) │
▼ │
┌─────────────────────┐ │
│ Ollama :11434 │ │
│ LM Studio :1234 │ │
│ llama.cpp :8080 │ │
└─────────────────────┘각 레이어는 독립적으로 테스트할 수 있습니다. 훅은 어디서든 실행 가능한 순수 함수 위에 구성됩니다.
use‑local‑llm을 선택해야 할 때
이상적인 경우
- 🚀 빠른 프로토타이핑 – 20분이 아니라 2분 안에 React 앱에서 AI 스트리밍을 시작하세요.
- 🔒 프라이버시 우선 앱 – 데이터가 절대 머신을 떠나지 않습니다. 클라우드 API 호출 없음. 추적 없음.
- 🏢 엔터프라이즈 / 오프라인 – 에어갭 네트워크와 연결이 끊긴 환경에서도 작동합니다.
- 🎓 학습 – 서버 보일러플레이트 없이 LLM 스트리밍 작동 방식을 이해하세요.
- ⚡ 작은 발자국 – 번들 크기와 의존성이 중요할 때.
이 라이브러리를 건너뛰어야 할 경우
- 프로덕션에서 OpenAI, Anthropic 등 클라우드 API를 사용하고 있다면.
- 서버‑사이드 인증, 로깅, 혹은 속도 제한이 필요하다면.
- 이미 다른 이유로 Vercel AI SDK를 사용 중이라면.
시작하기
1단계 – 라이브러리 설치
npm install use-local-llm2단계 – 로컬 LLM 시작
ollama serve3단계 – React 앱에서 AI 스트리밍
(위의 예시를 참고하세요.)
이게 전부입니다. API 라우트가 없습니다. 서버 설정이 필요 없습니다. 브라우저에서 2분 이내에 AI 스트리밍을 할 수 있습니다.
자세히 알아보기
- Full Documentation – API 레퍼런스, 고급 패턴, 문제 해결.
- Live Demo – Ollama와 함께 작동하는 모습을 확인하세요.
- GitHub – 소스 코드, 이슈, 기여.
- npm Package – 설치하기.
