Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save cognivator/ec38fea8564a3c370db2bef1de702f1e to your computer and use it in GitHub Desktop.
Save cognivator/ec38fea8564a3c370db2bef1de702f1e to your computer and use it in GitHub Desktop.
Previewjs JetBrains Plugin Configuration For NEXTjs

The culmination of multiple turns with Claude Desktop and MCP Servers for Sequential-Thinking, Desktop-Commander.

To understand this implementation and the problems it overcomes, refer to DEVTOOLS.md and README.md.

The files here are in a flat hierarchy, but their proper locations in a NEXT.js project are shown in __previewjs__README.md (which ultimately lands in the __previewjs__ folder.) DEVTOOLS.md can be placed anywhere in the project, but the root seems most useful.

DISCLAIMER

Standard disclaimer. This is a solution that worked for me. NO IMPLIED WARRANTY OR SUITABILITY TO PURPOSE. SUPPLIED AS-IS. Yada-yada (we've all seen this verbiage before).

Bottom line: I do not take any responsibility for damages caused directly or indirectly arising from your use of this example.

Development Tools Setup

Preview.js for Next.js Component Previews

This guide provides step-by-step instructions for setting up Preview.js to enable in-IDE previewing of Next.js components in JetBrains IDEs (WebStorm, IntelliJ IDEA).

Prerequisites

Required packages (install via npm/yarn/pnpm):

  • @previewjs/config - Preview.js configuration utilities
  • vite-plugin-svgr - SVG-to-React component transformation

Note: This solution does not make use of @previewjs/config-helper-nextjs package, which attempts an object property definition internally that fails.

Installation

  1. Install the Preview.js Plugin in your JetBrains IDE

    • Open your JetBrains IDE (WebStorm, IntelliJ IDEA)
    • Navigate to Settings/Preferences → Plugins → Marketplace
    • Search for "Preview.js"
    • Click "Install" and restart the IDE when prompted
  2. Install Required Packages for Next.js Support

    npm install --save-dev @previewjs/config vite-plugin-svgr
  3. Create the Project Configuration File Create preview.config.js in the project root (next to package.json):

    import { defineConfig } from '@previewjs/config';
    import path from 'path';
    import svgr from 'vite-plugin-svgr';
    
    export default defineConfig({
      // Configure public assets directory
      publicDir: 'public',
    
      // Set up a wrapper component for Next.js compatibility
      wrapper: {
        path: '__previewjs__/Wrapper.tsx',
        componentName: 'Wrapper',
      },
    
      // Vite configuration for module replacement and SVG handling
      vite: {
        // Add plugins for SVG React component support
        plugins: [
          // SVG plugin to transform SVGs into React components
          svgr({
            svgrOptions: {
              // SVGR options: https://react-svgr.com/docs/options/
              icon: true, // Set default size to 1em for icon-like behavior
              dimensions: false, // Remove width/height from SVG for responsive sizing
            },
            include: '**/*.svg', // Process all SVG files
          }),
        ],
    
        resolve: {
          alias: {
            // Replace Next.js modules with mock versions
            // This handles the module resolution BEFORE any non-configurable 
            // properties are created, avoiding runtime errors
            'next/image': path.resolve(__dirname, './__previewjs__/mocks/NextImageMock.jsx'),
            'next/router': path.resolve(__dirname, './__previewjs__/mocks/NextRouterMock.jsx'),
            'next/link': path.resolve(__dirname, './__previewjs__/mocks/NextLinkMock.jsx'),
          },
        },
        
        // Optional: Define environment variables for Preview.js
        define: {
          __PREVIEW_MODE__: true,
        },
    
        // Optional: Configure dev server options
        server: {
          // Customize port if needed
          port: 3333,
        },
      },
    });
  4. Create the Wrapper Directory and Mock Files

    mkdir -p __previewjs__/mocks

    The configuration automatically references mock files that provide Next.js compatibility. See __previewjs__/README.md for complete details on the wrapper and mock implementations.

  5. Solution Architecture

    This setup uses Vite module aliases instead of runtime property patching to avoid "Cannot redefine property: default" errors. Key components:

    • preview.config.js: Configures Vite aliases and SVG processing
    • Mock files: Provide Next.js-compatible implementations for Preview.js
    • Clean wrapper: Handles global styles and context providers only
    • SVG support: Transforms SVG imports into React components

    Important: The official approach using @previewjs/config-helper-nextjs and runtime property patching has been replaced with a more reliable build-time module replacement strategy.

    For complete implementation details, troubleshooting, and customization options, see: __previewjs__/README.md

Usage

  1. View a Component Preview

    • Open any React component file (.jsx or .tsx)
    • Look for the Preview.js icon in the editor gutter (left margin)
    • Click on this icon to open the Preview.js panel
    • Your component will be rendered in isolation
  2. Preview Components with Props

    • Preview.js will automatically generate props based on TypeScript types
    • You can modify these props in the UI to see different component states
  3. SVG Component Support

    • SVG files imported as React components (e.g., import Logo from "./logo.svg") now work correctly in Preview.js thanks to the vite-plugin-svgr configuration
  4. Customizing the Preview

    • If components require specific context providers, add them to your Wrapper component
    • For custom styling or themes, include them in the Wrapper
    • See __previewjs__/README.md for detailed customization options

Next.js Version Compatibility

  • Pages Router: The configuration above works with Next.js projects using the Pages Router
  • App Router: For projects using the newer App Router (Next.js 13+):
    • Preview.js is primarily designed for client-side components
    • Components using Next.js-specific server features may not preview correctly
    • The mock files handle most client-side Next.js components effectively

How It Works: Preview.js uses Vite's module resolution to replace Next.js modules with compatible mocks before any non-configurable properties are created. This approach:

  1. Replaces next/image, next/router, and next/link with Preview.js-compatible versions
  2. Transforms SVG files into React components using vite-plugin-svgr
  3. Provides a clean wrapper for global styles and context providers
  4. Avoids runtime property redefinition errors that plagued previous approaches

Troubleshooting

See __previewjs__/README.md for comprehensive troubleshooting information.

  1. "Cannot redefine property: default" Error

    • This error should no longer occur with the new Vite alias approach
    • If you see this error, ensure you're not using the old runtime patching method
    • Verify that preview.config.js is correctly configured with the Vite aliases
  2. SVG Import Errors

    • Ensure vite-plugin-svgr is installed: npm install --save-dev vite-plugin-svgr
    • Check that the plugin is properly configured in preview.config.js
    • SVG files should now import as React components automatically
  3. Missing Images

    • Ensure your publicDir is correctly set in preview.config.js
    • Verify that image paths are correct relative to the public directory
  4. Router-Related Errors

    • The new mock router should handle most use cases
    • For App Router projects, you may need additional mocks for next/navigation
    • See __previewjs__/mocks/NextRouterMock.jsx for the implementation
  5. Context Provider Errors

    • Add required providers to the Wrapper component in __previewjs__/Wrapper.tsx
    • Common contexts include theme providers, authentication contexts, and state management
  6. Styling Issues

    • Ensure global styles are imported in the Wrapper component
    • For Tailwind CSS, verify the import path in __previewjs__/Wrapper.tsx
    • CSS modules should work automatically with the new configuration

For additional help, consult:

  • __previewjs__/README.md - Complete solution documentation
  • __previewjs__/SVG_FIX.md - SVG-specific troubleshooting
  • Preview.js Documentation
import { defineConfig } from '@previewjs/config';
import path from 'path';
import svgr from 'vite-plugin-svgr';
export default defineConfig({
// Configure public assets directory
publicDir: 'public',
// Set up a wrapper component for Next.js compatibility
wrapper: {
path: '__previewjs__/Wrapper.tsx',
componentName: 'Wrapper',
},
// Vite configuration for module replacement and SVG handling
vite: {
// Add plugins for SVG React component support
plugins: [
// SVG plugin to transform SVGs into React components
svgr({
svgrOptions: {
// SVGR options: https://react-svgr.com/docs/options/
icon: true, // Set default size to 1em
dimensions: false, // Remove width/height from SVG
},
include: '**/*.svg', // Process all SVG files
}),
],
resolve: {
alias: {
// Replace Next.js modules with mock versions
// This handles the module resolution BEFORE any non-configurable
// properties are created, avoiding runtime errors
'next/image': path.resolve(__dirname, './__previewjs__/mocks/NextImageMock.jsx'),
'next/router': path.resolve(__dirname, './__previewjs__/mocks/NextRouterMock.jsx'),
'next/link': path.resolve(__dirname, './__previewjs__/mocks/NextLinkMock.jsx'),
},
},
// Optional: Define environment variables for Preview.js
define: {
__PREVIEW_MODE__: true,
},
// Optional: Configure dev server options
server: {
// Customize port if needed
port: 3333,
},
},
});
import React from 'react';
/**
* Mock for Next.js Image component
* Converts Next.js Image props to standard img element
*/
const NextImageMock = (props) => {
const {
src,
alt,
width,
height,
className,
style,
loading,
priority,
quality,
sizes,
fill,
unoptimized,
placeholder,
blurDataURL,
loader,
...rest
} = props;
// Convert Next.js specific props to standard img props
const imgProps = {
src,
alt: alt || '',
className,
style,
...rest
};
// Add dimensions if provided and not using fill
if (!fill) {
if (width) imgProps.width = width;
if (height) imgProps.height = height;
}
// Handle fill prop by adding appropriate styles
if (fill) {
imgProps.style = {
...imgProps.style,
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
objectFit: 'cover'
};
}
return <img {...imgProps} />;
};
export default NextImageMock;
import React from 'react';
/**
* Mock for Next.js Link component
* Converts Next.js Link to a standard anchor tag for Preview.js
*/
const NextLinkMock = ({
href,
as,
replace,
scroll,
shallow,
passHref,
prefetch,
locale,
children,
className,
style,
onClick,
...rest
}) => {
// Handle click events
const handleClick = (e) => {
e.preventDefault();
console.log('Link clicked:', { href, as });
// Call original onClick if provided
if (onClick) {
onClick(e);
}
};
// If children is a function, call it with the link props
if (typeof children === 'function') {
return children({
href: typeof href === 'object' ? href.pathname || '/' : href,
className,
style,
onClick: handleClick,
...rest
});
}
// If children is a single child element, clone it with link props
if (React.isValidElement(children)) {
return React.cloneElement(children, {
href: typeof href === 'object' ? href.pathname || '/' : href,
onClick: handleClick,
className: children.props.className || className,
style: { ...style, ...children.props.style },
...rest
});
}
// Default: render as anchor tag
return (
<a
href={typeof href === 'object' ? href.pathname || '/' : href}
className={className}
style={style}
onClick={handleClick}
{...rest}
>
{children}
</a>
);
};
export default NextLinkMock;
import React from 'react';
/**
* Mock for Next.js Router
* Provides all router functionality needed for Preview.js
*/
// Mock router object with all Next.js router properties
const mockRouter = {
route: '/',
pathname: '/',
query: {},
asPath: '/',
basePath: '',
locale: undefined,
locales: undefined,
defaultLocale: undefined,
isLocaleDomain: false,
isReady: true,
isFallback: false,
isPreview: false,
// Navigation methods
push: async (url, as, options) => {
console.log('Router.push called:', { url, as, options });
return true;
},
replace: async (url, as, options) => {
console.log('Router.replace called:', { url, as, options });
return true;
},
reload: () => {
console.log('Router.reload called');
},
back: () => {
console.log('Router.back called');
},
prefetch: async (url, as, options) => {
console.log('Router.prefetch called:', { url, as, options });
},
beforePopState: (callback) => {
console.log('Router.beforePopState called:', callback);
},
// Events system
events: {
on: (event, handler) => {
console.log('Router.events.on called:', { event, handler });
},
off: (event, handler) => {
console.log('Router.events.off called:', { event, handler });
},
emit: (event, ...args) => {
console.log('Router.events.emit called:', { event, args });
},
},
};
// Hook that returns the mock router
export const useRouter = () => {
return mockRouter;
};
// HOC that passes router as prop
export const withRouter = (Component) => {
const WrappedComponent = (props) => {
return <Component {...props} router={mockRouter} />;
};
WrappedComponent.displayName = `withRouter(${Component.displayName || Component.name})`;
return WrappedComponent;
};
// Default export for direct router access
export default mockRouter;

Preview.js Configuration for Next.js

This directory contains the configuration and mock files needed to make Preview.js work seamlessly with Next.js components.

Problem Solved

The original approach of using Object.defineProperty to patch Next.js modules at runtime failed with:

TypeError: Cannot redefine property: default

This error occurs because ES6 modules create non-configurable properties that cannot be redefined at runtime.

Solution

Instead of runtime property redefinition, we use Vite's resolve.alias to replace Next.js modules at the bundler level, before any non-configurable properties are created.

File Structure

__previewjs__/
├── README.md                 # This file
├── Wrapper.tsx              # Clean wrapper component (no module patching)
└── mocks/
    ├── NextImageMock.jsx    # Mock for next/image
    ├── NextRouterMock.jsx   # Mock for next/router  
    └── NextLinkMock.jsx     # Mock for next/link

preview.config.js            # Main configuration file

How It Works

  1. preview.config.js configures Vite aliases to replace Next.js modules
  2. Mock files provide compatible implementations for Preview.js
  3. Wrapper.tsx handles context providers and global styles only
  4. No runtime patching - everything happens at module resolution time

Key Benefits

Non-intrusive: No changes to your main webpack configuration
Reliable: Works at module resolution level, avoiding runtime errors
Maintainable: Clear separation between preview environment and production code
Project-independent: Works regardless of your main project's bundler

Configuration Details

preview.config.js

  • Uses Vite's resolve.alias to replace Next.js modules
  • Points to mock implementations in __previewjs__/mocks/
  • Configures wrapper component path

Mock Files

  • NextImageMock.jsx: Converts Next.js Image props to standard <img> elements
  • NextRouterMock.jsx: Provides mock router functionality with console logging
  • NextLinkMock.jsx: Converts Next.js Link to standard anchor tags

Wrapper.tsx

  • Clean component for global styles and context providers
  • NO module patching or Object.defineProperty calls
  • Add your theme providers, auth providers, etc. here

Usage

Components using next/image, next/router, or next/link will automatically use the mock versions when viewed in Preview.js, with no changes needed to your component code.

Extending

To add more Next.js module mocks:

  1. Create a new mock file in __previewjs__/mocks/
  2. Add an alias entry in preview.config.js
  3. Follow the same pattern as existing mocks

Troubleshooting

If you encounter module resolution issues:

  • Ensure mock file paths are correct in preview.config.js
  • Check that mock files export a default export
  • Verify TypeScript paths don't conflict (if using TypeScript)

SVG Component Support (Updated)

The configuration now includes support for SVG files imported as React components. The vite-plugin-svgr package has been installed and configured to transform SVG files into React components.

What's Included:

  • vite-plugin-svgr dependency installed
  • Plugin configured in preview.config.js with optimal settings
  • Automatic transformation of all *.svg imports into React components

Usage Examples:

// Both import methods work:
import Logo from "/path/to/logo.svg";                    // Default import
import { ReactComponent as Logo } from "/path/to/logo.svg"; // Named import

function Component() {
  return <Logo className="w-32 h-8" />;
}

See SVG_FIX.md for detailed troubleshooting information.

SVG Component Support Fix

Problem

SVG files imported as React components (e.g., import Logo from "path/to/image.svg") cause this error in Preview.js:

Error: Failed to execute 'createElement' on 'Document': The tag name provided ('/src/content/assets/images/henty-heuristics-logo-wide_clear.svg') is not a valid name.

Root Cause

Preview.js uses Vite internally, but SVG files are not being transformed into React components. Instead, they're being imported as URL strings, which React tries to use as component names.

Solution

1. Install vite-plugin-svgr

npm install --save-dev vite-plugin-svgr
# or
yarn add -D vite-plugin-svgr
# or  
pnpm add -D vite-plugin-svgr

2. Update preview.config.js

The preview.config.js file has been updated to include the vite-plugin-svgr plugin, which transforms SVG files into React components.

3. How It Works

  • vite-plugin-svgr uses SVGR under the hood to transform SVGs into React components
  • The plugin is configured with sensible defaults:
    • icon: true - Sets default size to 1em for icon-like behavior
    • dimensions: false - Removes fixed width/height from SVG for responsive sizing
    • include: '**/*.svg' - Processes all SVG files

4. Usage

After the fix, your existing SVG imports will work as expected:

// This will now work correctly in Preview.js
import Logo from "/src/content/assets/images/henty-heuristics-logo-wide_clear.svg";

function MyComponent() {
  return <Logo className="w-32 h-8" />;
}

5. Alternative Import Syntax

You can also use the explicit ReactComponent syntax if preferred:

import { ReactComponent as Logo } from "/path/to/logo.svg";

Files Modified

  • preview.config.js - Added vite-plugin-svgr plugin configuration

Dependencies Added

  • vite-plugin-svgr - Transforms SVG files into React components for Vite

The SVG handling is now fully integrated with the existing Next.js module mocking solution.

// __previewjs__/Wrapper.tsx
import React from 'react';
import '@/app/globals.css'; // Import global styles
// Clean wrapper component - NO module mocking here!
// All module mocking is handled by preview.config.js aliases
interface WrapperProps {
children: React.ReactNode;
}
export function Wrapper({ children }: WrapperProps) {
return (
<div className="preview-wrapper">
{/* Add any context providers here if needed */}
{/* Example providers (uncomment as needed):
<ThemeProvider theme={defaultTheme}>
<AuthProvider>
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
</AuthProvider>
</ThemeProvider>
*/}
{children}
</div>
);
}
export default Wrapper;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment