Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Antony-Figueroa/my-evershop-app/llms.txt

Use this file to discover all available pages before exploring further.

Page components are React components that render on specific pages of your EverShop store. They’re used in both extensions (for functionality) and themes (for design) to build dynamic, data-driven user interfaces.

Overview

Page components in EverShop:
  • Are React components with TypeScript support
  • Use a component area system to control placement
  • Fetch data via GraphQL queries
  • Support server-side rendering for better performance and SEO
  • Can be page-specific or global across all pages

Component Structure

Every page component must export three things:
  1. Default export - The React component function
  2. layout export - Placement configuration (area, order)
  3. query export (optional) - GraphQL query for data

Basic Template

import React from 'react';

type MyComponentProps = {
  // Define your props
};

export default function MyComponent({ }: MyComponentProps) {
  return (
    <div>
      {/* Your JSX */}
    </div>
  );
}

export const layout = {
  areaId: 'content',
  sortOrder: 10
};

export const query = `
  query Query {
    # Your GraphQL query
  }
`;

Directory Organization

Page components are organized by scope:
src/pages/
├── all/              # Global components (every page)
├── frontStore/       # Front-end only
├── admin/            # Admin panel only
├── homepage/         # Homepage specific
├── productView/      # Product page specific
└── account/          # Account pages

Scope Hierarchy

  • all/ - Renders on every page
  • frontStore/ - Renders on all front-end pages
  • admin/ - Renders on all admin pages
  • [page-name]/ - Renders on specific page only

Real-World Examples

Simple List Component

Here’s a complete example from the sample extension:
extensions/sample/src/pages/frontStore/homepage/FooList.tsx
import React from 'react';

type FooListProps = {
  foos?: {
    id: number;
    name: string;
    description: string;
  }[];
};

export default function FooList({ foos }: FooListProps) {
  return (
    <div className="foo-list container mx-auto px-4 py-8">
      <h2 className="font-bold text-center mb-8">Foo List</h2>
      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
        {foos?.map((foo) => (
          <div
            key={foo.id}
            className="bg-white rounded-lg shadow-md p-6 hover:shadow-lg transition-shadow duration-300"
          >
            <h3 className="font-semibold mb-3 text-gray-800">{foo.name}</h3>
            <p className="text-gray-600 leading-relaxed">{foo.description}</p>
          </div>
        ))}
      </div>
    </div>
  );
}

export const layout = {
  areaId: 'content',
  sortOrder: 30
};

export const query = `
  query Query {
    foos {
      id
      name
      description
    }
  }
`;

Header Component

A global header component from the anasuplements theme:
themes/anasuplements/src/pages/all/Header.tsx
import React from 'react';

type HeaderProps = {
  storeName?: string;
  categories?: {
    name: string;
    url: string;
  }[];
};

export default function Header({ 
  storeName = "My Store", 
  categories = [] 
}: HeaderProps) {
  return (
    <header className="bg-[#F8FAF9] border-b border-[#E8F5E9]">
      <div className="container mx-auto px-4 py-4">
        <div className="flex items-center justify-between">
          <a href="/" className="text-2xl font-bold text-[#2D5A3D]">
            {storeName}
          </a>
          
          <nav className="hidden md:flex space-x-6">
            {categories.map((category, index) => (
              <a 
                key={index} 
                href={category.url}
                className="text-[#4A5568] hover:text-[#2D5A3D] transition-colors"
              >
                {category.name}
              </a>
            ))}
          </nav>

          <div className="flex items-center space-x-4">
            <a href="/cart" className="text-[#2D5A3D]">
              {/* Cart Icon SVG */}
            </a>
            <a href="/account" className="text-[#2D5A3D]">
              {/* Account Icon SVG */}
            </a>
          </div>
        </div>
      </div>
    </header>
  );
}

export const layout = {
  areaId: 'header',
  sortOrder: 1
};

Product Page Component

A component that displays product information:
extensions/productCatalog/src/pages/frontStore/productView/SupplementInfo.tsx
import React from 'react';

type SupplementInfoProps = {
  product: {
    productId: string;
    name: string;
    description?: string;
    extension?: {
      supplement?: {
        ingredients?: string;
        benefits?: string[];
        presentation?: string;
        dosage?: string;
        warnings?: string;
        storage?: string;
      };
    };
  };
};

export default function SupplementInfo({ product }: SupplementInfoProps) {
  const supplementData = product?.extension?.supplement;

  if (!supplementData && !product?.description) {
    return null;
  }

  return (
    <div className="bg-[#F8FAF9] border border-[#E8F5E9] rounded-lg p-6 mt-6">
      <h3 className="text-xl font-bold text-[#2D5A3D] mb-4">
        Información del Suplemento
      </h3>
      
      {supplementData && (
        <div className="space-y-4">
          {supplementData.ingredients && (
            <div>
              <h4 className="font-semibold text-[#2D5A3D] mb-2">Ingredientes</h4>
              <p className="text-[#4A5568] text-sm">{supplementData.ingredients}</p>
            </div>
          )}
          
          {supplementData.benefits && (
            <div>
              <h4 className="font-semibold text-[#2D5A3D] mb-2">Beneficios</h4>
              <ul className="list-disc list-inside text-[#4A5568] text-sm space-y-1">
                {supplementData.benefits.map((benefit: string, index: number) => (
                  <li key={index}>{benefit}</li>
                ))}
              </ul>
            </div>
          )}
          
          {supplementData.dosage && (
            <div>
              <h4 className="font-semibold text-[#2D5A3D] mb-2">Dosis Recomendada</h4>
              <p className="text-[#4A5568] text-sm">{supplementData.dosage}</p>
            </div>
          )}
        </div>
      )}
    </div>
  );
}

export const layout = {
  areaId: 'productPageMiddleRight',
  sortOrder: 10
};

export const query = `
  query Query {
    product(id: getContextValue("productId")) {
      productId
      name
      description
      extension {
        supplement {
          ingredients
          benefits
          presentation
          dosage
          warnings
          storage
        }
      }
    }
  }
`;

Layout Configuration

The layout export controls where and when your component renders:
export const layout = {
  areaId: 'content',    // Where to render
  sortOrder: 10         // Order within area (lower = earlier)
};

Common Areas

Area IDLocationUsage
headerTop of pageSite header, navigation
contentMain contentPrimary page content
sidebarSide columnFilters, widgets
footerBottom of pageSite footer, links
productPageMiddleRightProduct page rightProduct details, actions
productPageMiddleLeftProduct page leftProduct images, gallery
Sort Order: Use increments of 10 (10, 20, 30) to allow inserting components in between later.

Custom Areas

You can also create custom areas in your components:
export default function MyComponent() {
  return (
    <div>
      <Area id="customArea" />
    </div>
  );
}

Working with GraphQL

Basic Query

export const query = `
  query Query {
    products {
      productId
      name
      price
    }
  }
`;

Query with Parameters

Use context values for dynamic data:
export const query = `
  query Query {
    product(id: getContextValue("productId")) {
      productId
      name
      description
      price
    }
  }
`;

Nested Data

Fetch related data:
export const query = `
  query Query {
    product(id: getContextValue("productId")) {
      productId
      name
      price
      category {
        categoryId
        name
      }
      images {
        url
        alt
      }
    }
  }
`;
Props are automatically passed to your component based on the GraphQL query results.

TypeScript Types

Always define types for your props:
type ProductCardProps = {
  product: {
    productId: string;
    name: string;
    price: number;
    image?: {
      url: string;
      alt: string;
    };
  };
};

export default function ProductCard({ product }: ProductCardProps) {
  // TypeScript ensures type safety
}

Conditional Rendering

Null Checks

export default function SupplementInfo({ product }: SupplementInfoProps) {
  const supplementData = product?.extension?.supplement;

  if (!supplementData) {
    return null;  // Don't render if no data
  }

  return (
    <div>{/* Render component */}</div>
  );
}

Conditional Content

export default function ProductInfo({ product }: ProductInfoProps) {
  return (
    <div>
      <h1>{product.name}</h1>
      
      {product.description && (
        <p>{product.description}</p>
      )}
      
      {product.inStock ? (
        <button>Add to Cart</button>
      ) : (
        <span>Out of Stock</span>
      )}
    </div>
  );
}

Styling Components

Use Tailwind CSS for styling:

Responsive Grid

<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
  {items.map(item => (
    <div key={item.id} className="bg-white rounded-lg shadow p-6">
      {/* Item content */}
    </div>
  ))}
</div>

Hover Effects

<div className="hover:shadow-lg transition-shadow duration-300">
  <a href="/product" className="text-gray-800 hover:text-[#2D5A3D]">
    Product Name
  </a>
</div>

Custom Colors

<div className="bg-[#F8FAF9] border-[#E8F5E9] text-[#2D5A3D]">
  {/* Content */}
</div>

Best Practices

Performance: Only query the data you need. Avoid fetching unnecessary fields.
  • Type Safety - Always define TypeScript types for props
  • Null Checks - Use optional chaining (?.) for nested data
  • Semantic HTML - Use proper HTML elements (<header>, <nav>, <article>)
  • Accessibility - Add ARIA labels and proper alt text
  • Responsive - Design mobile-first with Tailwind breakpoints
  • Performance - Optimize images and minimize re-renders

Common Patterns

List with Empty State

export default function ProductList({ products }: ProductListProps) {
  if (!products || products.length === 0) {
    return (
      <div className="text-center py-12">
        <p className="text-gray-500">No products found.</p>
      </div>
    );
  }

  return (
    <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
      {products.map(product => (
        <ProductCard key={product.id} product={product} />
      ))}
    </div>
  );
}

Loading State

export default function DataComponent({ data, loading }: Props) {
  if (loading) {
    return <div className="animate-pulse">Loading...</div>;
  }

  return <div>{/* Render data */}</div>;
}

Error Boundaries

export default function SafeComponent({ data }: Props) {
  try {
    return (
      <div>
        {/* Component content */}
      </div>
    );
  } catch (error) {
    console.error('Component error:', error);
    return <div>Something went wrong</div>;
  }
}

Troubleshooting

Component Not Rendering

  1. Verify layout export exists
  2. Check areaId is valid for the page
  3. Ensure component is in correct directory
  4. Run npm run build to recompile

Props Are Undefined

  1. Verify GraphQL query syntax
  2. Check field names match schema
  3. Ensure query returns data
  4. Add null checks with optional chaining

Styles Not Applying

  1. Verify Tailwind class names
  2. Check for typos
  3. Use bracket notation for custom colors
  4. Clear browser cache

Next Steps

Creating Themes

Learn about theme architecture

GraphQL Reference

Explore the GraphQL schema

Build docs developers (and LLMs) love