Skip to main content

Building Interactive Documentation with Docusaurus BrowserOnly

· 3 min read
Justin O'Connor
Founder @ Onward Platforms

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 or document)
  • 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

  1. Fallback Content: Always provide a fallback for the static build:

    <BrowserOnly fallback={<div>Loading...</div>}>
  2. 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>
    );
  3. Performance: Load heavy dependencies only in the browser:

    <BrowserOnly>
    {() => {
    // Dynamic import heavy libraries
    const { HeavyComponent } = require('./HeavyComponent');
    return <HeavyComponent />;
    }}
    </BrowserOnly>

Common Gotchas

  1. 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>
  2. 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.