Click-to-code for Next.js. See exactly where your React components live and jump to their source with a single click.
Press Shift+Z to see through your UI. Click any element to instantly open it in your editor. No more hunting through files.
- 🚀 Turbopack Compatible - Works seamlessly with Next.js 15's Turbopack
- ⚛️ React 19 Support - Full compatibility with React 19 features
- 📍 Source Location Injection - Adds
data-insp-pathattributes in format/absolute/path/file.tsx:line:column - 🛠️ Developer Tools - Runtime utilities for source navigation
- 🎯 Click-to-Code - Open source files directly in your editor
- 🔧 Configurable - Flexible options for different use cases
- 🏃 Zero Runtime Overhead - Attributes only added in development
npm install --save-dev gaze
# or
yarn add -D gaze
# or
pnpm add -D gazepnpm add -D github:your-username/gaze// In your package.json
{
"devDependencies": {
"gaze": "file:../path/to/gaze"
}
}Configure once in next.config.mjs, and the settings are automatically applied:
// next.config.mjs
const nextConfig = {
turbopack: {
rules: {
'*.{jsx,tsx}': {
loaders: [
{
loader: 'gaze',
options: {
runtime: {
editor: 'vscode', // Your editor: 'vscode' | 'cursor' | 'webstorm' | etc.
tooltipPathDisplay: 'relative' // Show short paths in tooltips (optional)
}
}
}
]
}
}
}
};
export default nextConfig;// app/layout.tsx (Server Component - recommended approach)
import { GazeInitializer } from './gaze-initializer';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
{children}
{process.env.NODE_ENV === 'development' && <GazeInitializer />}
</body>
</html>
);
}
// app/gaze-initializer.tsx (Client Component - isolated from layout)
'use client';
import { useEffect } from 'react';
import { enableSourceHighlighting } from 'gaze/runtime';
export function GazeInitializer() {
useEffect(() => {
if (process.env.NODE_ENV === 'development') {
const cleanup = enableSourceHighlighting({});
return cleanup;
}
}, []);
return null;
}
// Alternative: All-in-one client-side approach
// app/layout.tsx
'use client';
import { useEffect } from 'react';
import { enableSourceHighlighting } from 'gaze/runtime';
export default function RootLayout({ children }: { children: React.ReactNode }) {
useEffect(() => {
if (process.env.NODE_ENV === 'development') {
const cleanup = enableSourceHighlighting({}); // Uses config from next.config.mjs
return cleanup;
}
}, []);
return (
<html lang="en">
<body>{children}</body>
</html>
);
}Done! Press Shift+Z to toggle, click any element to open in your editor.
Note: The
enableSourceHighlighting({})function automatically uses the configuration from yournext.config.mjs. You can pass additional options to override specific settings if needed.
All available options with examples:
const nextConfig = {
turbopack: {
rules: {
'*.{jsx,tsx}': {
loaders: [
{
loader: 'gaze',
options: {
// Core Options
production: false, // Enable in production (default: false)
attributeName: 'data-insp-path', // HTML attribute name (default: 'data-insp-path')
useRelativePaths: false, // Use relative paths (default: false - absolute paths)
rootDir: process.cwd(), // Root for relative paths
injectReactSource: false, // Add React __source (default: false for Turbopack)
// Runtime Configuration - these settings are passed to enableSourceHighlighting()
runtime: {
autoInject: false, // Must be false for Next.js App Router
editor: 'cursor', // Editor preset: 'vscode' | 'cursor' | 'webstorm' | etc.
tooltipPathDisplay: 'relative', // Show relative paths in tooltips
enabledByDefault: false, // Start with highlighting off
showInstructions: true, // Show console instructions
styles: {
outline: '2px solid #0066cc',
outlineOffset: '2px'
}
}
}
}
]
}
}
}
};
export default nextConfig;For projects not yet using Turbopack:
module.exports = {
webpack: (config, { dev }) => {
if (dev) {
config.module.rules.push({
test: /\.(jsx|tsx)$/,
exclude: /node_modules/,
use: ['gaze']
});
}
return config;
}
};import { useEffect } from 'react';
import { enableSourceHighlighting } from 'gaze/runtime';
export default function MyApp({ Component, pageProps }) {
useEffect(() => {
// Enable source highlighting in development
if (process.env.NODE_ENV === 'development') {
const cleanup = enableSourceHighlighting();
return cleanup;
}
}, []);
return <Component {...pageProps} />;
}import { createSourceClickHandler } from 'gaze/runtime';
export function DebugButton({ children }) {
const handleSourceClick = createSourceClickHandler('vscode');
return (
<button
onClick={(e) => {
if (e.metaKey || e.ctrlKey) {
handleSourceClick(e);
}
}}
>
{children}
</button>
);
}'use client';
import { useEffect } from 'react';
import { enableSourceHighlighting } from 'gaze/runtime';
export default function Layout({ children }) {
useEffect(() => {
if (process.env.NODE_ENV === 'development') {
const cleanup = enableSourceHighlighting({
editor: 'vscode', // or 'cursor', 'webstorm', etc.
enabledByDefault: false
});
return cleanup;
}
}, []);
return children;
}Usage:
- Press
Shift+Zto toggle source highlighting - Click any highlighted element to open in your editor
// Built-in editor presets
enableSourceHighlighting({ editor: 'vscode' }); // VS Code
enableSourceHighlighting({ editor: 'cursor' }); // Cursor
enableSourceHighlighting({ editor: 'webstorm' }); // WebStorm
enableSourceHighlighting({ editor: 'sublime' }); // Sublime Text
// Custom editor configuration
enableSourceHighlighting({
editor: {
handler: 'myeditor://open?file={file}&line={line}',
useAbsolutePath: true
}
});
// Full configuration example
enableSourceHighlighting({
editor: 'cursor',
enabledByDefault: true, // Start with highlighting on
attributeName: 'data-insp-path', // Custom attribute name
styles: {
outline: '3px solid #00ff00', // Green outline
outlineOffset: '4px',
tooltipBackground: 'rgba(0, 255, 0, 0.9)',
tooltipColor: 'white'
}
});Control how file paths appear in hover tooltips while keeping full paths for editor integration:
// Show relative paths in tooltips (cleaner UI)
enableSourceHighlighting({
tooltipPathDisplay: 'relative' // Shows: "app/page.tsx:10:5"
});
// Show absolute paths in tooltips (default)
enableSourceHighlighting({
tooltipPathDisplay: 'absolute' // Shows: "/Users/name/project/app/page.tsx:10:5"
});Note: Regardless of the tooltip display setting, clicking always uses the full absolute path to ensure your editor can properly open the file.
| Option | Type | Default | Description |
|---|---|---|---|
production |
boolean |
false |
Enable in production builds |
attributeName |
string |
'data-insp-path' |
HTML attribute name |
useRelativePaths |
boolean |
false |
Use relative paths (false = absolute paths) |
rootDir |
string |
process.cwd() |
Root directory for paths |
injectReactSource |
boolean |
false |
Add React __source prop (disabled by default to avoid Turbopack conflicts) |
Parse a source location string.
Get source location from a DOM element.
Find source location in element or ancestors.
Open source file in editor. Supports: vscode, cursor, webstorm, fleet.
Enable source highlighting with Shift+Z toggle. Returns cleanup function.
When called with an empty object {}, it automatically uses the configuration from your next.config.mjs runtime options. Any config passed directly will override the loader config.
Config options:
editor: Editor preset name or custom configenabledByDefault: Start with highlighting ontooltipPathDisplay: How to display paths in tooltips ('absolute' | 'relative')attributeName: Attribute to look forstyles: Custom highlight styles
Create click handler for opening source in editor.
Get all elements with source location metadata.
- Build Time: The loader parses JSX/TSX files using Babel
- AST Transform: Injects attributes into JSX opening elements
- Configuration: Runtime config from
next.config.mjsis injected into your code - Runtime: When you call
enableSourceHighlighting({}), it automatically uses the injected config - Output: Transformed code with source location metadata and configuration
Input:
// components/Button.tsx - Line 5
function Button({ children }) {
return (
<button className="btn">
{children}
</button>
);
}Output HTML:
<button
class="btn"
data-insp-path="/Users/username/project/components/Button.tsx:3:5"
>
Click me
</button>Source locations are injected for both Server and Client components:
// Server Component
export default async function Page() {
return <div>Server rendered with source location</div>;
}
// Client Component
'use client';
export function ClientComponent() {
return <div>Client rendered with source location</div>;
}Full support for Next.js 15 App Router:
// app/layout.tsx
export default function RootLayout({ children }) {
return (
<html>
<body>
{children}
{process.env.NODE_ENV === 'development' && <DevSourceToolbar />}
</body>
</html>
);
}- Development Only: By default, disabled in production
- Build Time: Minimal impact on build performance
- Runtime: No overhead when disabled
- Bundle Size: Attributes stripped in production builds
- Ensure the loader is installed:
npm ls gaze - Check file extensions match the rule pattern
- Verify Turbopack is enabled in Next.js
- Clear
.nextcache and rebuild
- Check browser DevTools for
data-source-locationattributes - Ensure you're in development mode
- Verify the loader is in your config
- Check for conflicting loaders
- Ensure your editor protocol handler is installed
- Check the editor is running
- Try different editor options
- Verify file paths are correct
If you get a "Duplicate __source is found" error with Next.js 15 and Turbopack:
- Set
injectReactSource: falsein the loader options - Turbopack already includes React's development transforms
- Our loader should only inject the
data-insp-pathattribute
If you see errors like "Can't resolve './Component.tsx.tsx'":
- Remove the
as: '*.tsx'option from your Turbopack configuration - Let Turbopack preserve the original file extensions
- The loader outputs valid JSX/TSX that Turbopack can process
If you get "Module not found: Can't resolve 'gaze/runtime'":
- Ensure the package is properly installed with
pnpm install - The package.json must have proper
exportsfield configuration - For local development, use
file:protocol in package.json dependencies - Try importing from the absolute path if subpath exports aren't working
- ✅ Next.js 15+
- ✅ React 19
- ✅ Turbopack
- ✅ TypeScript
- ✅ JavaScript
- ✅ Server Components
- ✅ Client Components
- ✅ App Router
- ✅ Pages Router
Contributions are welcome! Please read our contributing guidelines before submitting PRs.
MIT