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:
Default export - The React component function
layout export - Placement configuration (area, order)
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
}
}
` ;
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 ID Location Usage headerTop of page Site header, navigation contentMain content Primary page content sidebarSide column Filters, widgets footerBottom of page Site footer, links productPageMiddleRightProduct page right Product details, actions productPageMiddleLeftProduct page left Product 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
Verify layout export exists
Check areaId is valid for the page
Ensure component is in correct directory
Run npm run build to recompile
Props Are Undefined
Verify GraphQL query syntax
Check field names match schema
Ensure query returns data
Add null checks with optional chaining
Styles Not Applying
Verify Tailwind class names
Check for typos
Use bracket notation for custom colors
Clear browser cache
Next Steps
Creating Themes Learn about theme architecture
GraphQL Reference Explore the GraphQL schema