Logo
  1. Docs
  2. Guides

Project Structure

Published on Nov 20, 2023, updated 3 weeks ago

Overview

The Weaverse Hydrogen theme follows a modern Shopify Hydrogen structure, optimized for performance and developer experience. Let's explore the key components of your theme.

Core Structure

🌳 my-theme├── 📁 app/│   ├── 📁 components/     # Reusable UI components│   ├── 📁 graphql/       # GraphQL queries and fragments│   ├── 📁 hooks/         # Custom React hooks│   ├── 📁 routes/        # Application routes│   ├── 📁 sections/      # Theme sections│   ├── 📁 styles/        # Global styles and Tailwind│   ├── 📁 types/         # TypeScript definitions│   ├── 📁 utils/         # Helper functions│   ├── 📁 weaverse/      # Weaverse configuration│   ├── 📄 entry.client.tsx│   ├── 📄 entry.server.tsx│   └── 📄 root.tsx├── 📁 public/            # Static assets├── 📄 server.ts          # Server configuration├── 📄 vite.config.ts     # Build configuration├── 📄 tailwind.config.js # Tailwind settings└── 📄 .env              # Environment variables

Key Directories

/app/components

Reusable UI components:

// Example: app/components/Button.tsxexport function Button({ children, className = '', ...props }) {  return (    <button       className={`px-4 py-2 bg-primary text-white ${className}`}       {...props}    >      {children}    </button>  );}

/app/sections

Theme sections for Weaverse Studio:

// Example: app/sections/Hero.tsximport { forwardRef } from 'react';
export type HeroProps = {  heading: string;  description: string;  className?: string;};
export let schema = {  title: 'Hero',  type: 'hero',  inspector: [    {      group: 'Content',      inputs: [        { type: 'text', name: 'heading', label: 'Heading' },        { type: 'textarea', name: 'description', label: 'Description' }      ]    }  ]};
export let Hero = forwardRef<HTMLElement, HeroProps>((props, ref) => {  let { heading, description, className = '' } = props;    return (    <section       ref={ref}      className={`py-12 px-4 max-w-7xl mx-auto ${className}`}    >      <div className="text-center">        <h1 className="text-4xl font-bold tracking-tight sm:text-6xl">          {heading}        </h1>        <p className="mt-6 text-lg leading-8 text-gray-600">          {description}        </p>      </div>    </section>  );});
Hero.displayName = 'Hero';

/app/weaverse

Core Weaverse configuration files:

  • components.ts - Component registry
  • schema.server.ts - Theme schema
  • create-weaverse.server.ts - Client setup

Essential Files

server.ts

Server configuration and Weaverse client integration:

import { WeaverseClient } from '@weaverse/hydrogen';import { components } from '~/weaverse/components';import { themeSchema } from '~/weaverse/schema.server';
export async function createAppLoadContext(request, env, executionContext) {  // Initialize Hydrogen context  let hydrogenContext = createHydrogenContext({    env,    request,    cache,    waitUntil,    session,    i18n: getLocaleFromRequest(request),    cart: { queryFragment: CART_QUERY_FRAGMENT },  });
  // Initialize Weaverse client  return {    ...hydrogenContext,    weaverse: new WeaverseClient({      ...hydrogenContext,      request,      cache,      themeSchema,      components,    }),  };}

entry.server.tsx

Server-side rendering setup:

import { RemixServer } from "@remix-run/react";import { createContentSecurityPolicy } from "@shopify/hydrogen";import type { AppLoadContext, EntryContext } from "@shopify/remix-oxygen";import { isbot } from "isbot";import { renderToReadableStream } from "react-dom/server";import { getWeaverseCsp } from "~/weaverse/csp";
export default async function handleRequest(  request: Request,  responseStatusCode: number,  responseHeaders: Headers,  remixContext: EntryContext,  context: AppLoadContext,) {  const { nonce, header, NonceProvider } = createContentSecurityPolicy({    ...getWeaverseCsp(request, context),    shop: {      checkoutDomain: context.env?.PUBLIC_CHECKOUT_DOMAIN || context.env?.PUBLIC_STORE_DOMAIN,      storeDomain: context.env?.PUBLIC_STORE_DOMAIN,    },  });
  const body = await renderToReadableStream(    <NonceProvider>      <RemixServer context={remixContext} url={request.url} nonce={nonce} />    </NonceProvider>,    {      nonce,      signal: request.signal,      onError(error) {        console.error(error);        responseStatusCode = 500;      },    },  );
  if (isbot(request.headers.get("user-agent"))) {    await body.allReady;  }
  responseHeaders.set("Content-Type", "text/html");  responseHeaders.set("Content-Security-Policy-Report-Only", header);
  return new Response(body, {    headers: responseHeaders,    status: responseStatusCode,  });}

.env

Required environment variables:

# Core ConfigurationSESSION_SECRET="foobar"
# Shopify ConfigurationPUBLIC_STORE_DOMAIN=your-store.myshopify.comPUBLIC_STOREFRONT_API_TOKEN=your-tokenPUBLIC_CUSTOMER_ACCOUNT_API_CLIENT_ID=your-client-idSHOP_ID=your-shop-idPUBLIC_CHECKOUT_DOMAIN=your-checkout-domain
# Optional Shopify ConfigurationPUBLIC_STOREFRONT_ID=your-storefront-id# PRIVATE_STOREFRONT_API_TOKEN=your-private-token
# Weaverse ConfigurationWEAVERSE_PROJECT_ID=your-project-id# WEAVERSE_API_KEY=your-api-key
# Additional Services (Optional)# PUBLIC_GOOGLE_GTM_ID=your-gtm-id# JUDGEME_PRIVATE_API_TOKEN=your-judgeme-token# ALI_REVIEWS_API_KEY=your-ali-reviews-key
# Custom Metafields & MetaobjectsMETAOBJECT_COLORS_TYPE=shopify--color-patternCUSTOM_COLLECTION_BANNER_METAFIELD=custom.collection_banner
# Shopify Inbox (Optional)# PUBLIC_SHOPIFY_INBOX_SHOP_ID=your-inbox-shop-id

Development Commands

# Install dependenciesnpm install
# Start developmentnpm run dev
# Build for productionnpm run build
# Preview production buildnpm run preview

Next Steps

Need help? Join our Community Slack.


Was this article helpful?