Using React in Astro
This is the headline feature: your existing React components work inside Astro with zero modifications. No rewriting, no converting to .astro. Add the integration, import the file, add a directive — done.
Step 1: Add the React integration
Section titled “Step 1: Add the React integration”Run this in your Astro project:
npx astro add reactThis installs @astrojs/react, react, and react-dom, then patches astro.config.mjs for you automatically.
Run locally after
npx astro add react. React islands require the@astrojs/reactintegration and a separate.jsx/.tsxfile — they cannot run in the minimal StackBlitz playground used by this course.
Step 2: Write a React component
Section titled “Step 2: Write a React component”Your React components are plain .jsx or .tsx files. Nothing special needed:
import { useState } from 'react';
export default function Counter({ start = 0 }) { const [count, setCount] = useState(start); return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>+1</button> </div> );}Step 3: Use it in an .astro page
Section titled “Step 3: Use it in an .astro page”Import the .jsx file into a .astro page and add a client: directive:
---import Counter from '../components/Counter.jsx';---<html lang="en"> <head><meta charset="utf-8" /><title>Counter demo</title></head> <body> <h1>My Page</h1> <p>This heading is static HTML — zero JS.</p> <Counter client:load start={5} /> </body></html>Without client:load the component renders to static HTML (no interactivity). Add the directive and Astro ships the React runtime + your component JS to the browser and hydrates it.
Side by side: React app vs Astro page with a React island
Section titled “Side by side: React app vs Astro page with a React island”// React app — everything in one tree// src/App.jsximport Counter from './Counter';import Header from './Header';
export default function App() { return ( <> <Header /> {/* part of React tree */} <Counter start={5} /> {/* part of React tree */} </> );}---// Astro page — Header is .astro (zero JS),// Counter is a React islandimport Header from '../components/Header.astro';import Counter from '../components/Counter.jsx';---<html lang="en"> <body> <Header /> <!-- static HTML --> <Counter client:load start={5} /> <!-- React island --> </body></html>Your whole React codebase can move over
Section titled “Your whole React codebase can move over”Hooks, context, third-party React libraries, component composition — all of it works inside a React island exactly as it does in a React app. You are not giving anything up; you are just choosing which parts of the page need it.
// src/components/SearchBar.jsx — a real-world islandimport { useState, useCallback } from 'react';import { useDebounce } from 'use-debounce'; // third-party hook
export default function SearchBar({ onSearch }) { const [query, setQuery] = useState(''); const [debouncedQuery] = useDebounce(query, 300);
// useEffect, context, custom hooks — all fine inside an island return ( <input value={query} onChange={e => setQuery(e.target.value)} placeholder="Search..." /> );}