Building Interactive Documentation with Docusaurus BrowserOnly
When building documentation with Docusaurus, you might find yourself needing interactive features that can only work in the browser. In this post, I'll share how we used Docusaurus's BrowserOnly
component to create our API Playground while maintaining static site generation compatibility.
The Challenge with Static Sites
Docusaurus generates static sites, which means all pages are pre-rendered during build time. This presents a challenge when you need dynamic features like:
- Making API calls
- Accessing browser APIs (like
window
ordocument
) - Using browser-specific React hooks
- Handling user interactions
This is where BrowserOnly
comes in.
Understanding BrowserOnly
The BrowserOnly
component from Docusaurus ensures that its children are only rendered in the browser, not during static site generation. Here's a basic example:
import BrowserOnly from '@docusaurus/BrowserOnly';
<BrowserOnly>
{() => {
// This code only runs in the browser
const [state, setState] = useState(null);
return <div>Browser-only content</div>;
}}
</BrowserOnly>
Real-World Example: Our API Playground
We used BrowserOnly
to build our Terraform Editor API Playground. Here's how we structured it:
import BrowserOnly from '@docusaurus/BrowserOnly';
import { JsonTextArea } from '../components/ui/jsontextarea';
<BrowserOnly fallback={<div>Loading playground...</div>}>
{() => {
const [terraformCode, setTerraformCode] = useState(initialCode);
const [editOperations, setEditOperations] = useState(initialEdits);
const handleSubmit = async () => {
// Make live API calls safely in the browser
const response = await fetch('https://api.example.com/edit', {
method: 'POST',
body: JSON.stringify({ code: terraformCode, edits: editOperations }),
});
// Handle response...
};
return (
<div>
<JsonTextArea
value={terraformCode}
onChange={setTerraformCode}
/>
<button onClick={handleSubmit}>
Make API Request
</button>
</div>
);
}}
</BrowserOnly>
Key Benefits and Best Practices
-
Fallback Content: Always provide a fallback for the static build:
<BrowserOnly fallback={<div>Loading...</div>}>
-
Component Organization: Keep browser-specific components separate:
// ✅ Good: Isolated browser-specific code
const BrowserComponent = () => (
<BrowserOnly>
{() => <ComplexInteractiveFeature />}
</BrowserOnly>
);
// ❌ Bad: Mixed concerns
const Component = () => (
<div>
<StaticContent />
<BrowserOnly>{() => <InteractiveFeature />}</BrowserOnly>
</div>
); -
Performance: Load heavy dependencies only in the browser:
<BrowserOnly>
{() => {
// Dynamic import heavy libraries
const { HeavyComponent } = require('./HeavyComponent');
return <HeavyComponent />;
}}
</BrowserOnly>
Common Gotchas
-
Accessing Window/Document: Don't access browser APIs outside
BrowserOnly
:// ❌ Will break in build
const width = window.innerWidth;
// ✅ Safe usage
<BrowserOnly>
{() => {
const width = window.innerWidth;
return <div>Width: {width}</div>;
}}
</BrowserOnly> -
React Hooks: Ensure hooks are only used within the browser context:
// ✅ Safe hook usage
<BrowserOnly>
{() => {
const [state, setState] = useState(null);
useEffect(() => {
// Browser-safe side effects
}, []);
return <Component />;
}}
</BrowserOnly>
Conclusion
BrowserOnly
is a powerful tool that lets you build rich, interactive documentation while maintaining the benefits of static site generation. You can see it in action in our API Playground, where we use it to provide a live testing environment for our Terraform Editor API.
For more examples and detailed documentation, check out the official Docusaurus docs.