Automatically Generate TypeScript Types for Your Shopify Storefront Queries

Say goodbye to manual typing — build a fully type-safe headless Shopify storefront using GraphQL Code Generator.

·Matija Žiberna·
Automatically Generate TypeScript Types for Your Shopify Storefront Queries

Building a headless Shopify storefront with Next.js should be exciting, but I found myself trapped in a frustrating cycle that was draining my productivity and confidence. Every time I needed to fetch products, manage cart operations, or query collections, I faced the same tedious process: write a GraphQL query, then manually craft TypeScript types that I hoped would match Shopify's ever-evolving schema.

The reality was far from ideal. I'd spend hours carefully typing out interfaces, second-guessing whether Shopify returned amount as a string or number, whether fields were nullable, or what the exact structure of nested objects looked like. Worse yet, when I updated queries to add new fields or optimize performance, I'd inevitably forget to update the corresponding types. This led to a cascade of silent failures, mysterious runtime errors, and late-night debugging sessions trying to figure out why my perfectly valid-looking code was breaking in production.

What started as a simple e-commerce project became a maintenance nightmare. I was spending more time managing types than building features, and every schema change from Shopify felt like a potential disaster waiting to happen. There had to be a better way.

Before: Manual types that get outdated, are prone to errors, and require constant maintenance - turning feature development into a frustrating guessing game.

After: Automatically generated types that are always in sync with your GraphQL operations and Shopify's schema - letting you focus on building great user experiences instead of wrestling with type definitions.

What We'll Build

A complete GraphQL code generation system that:

  • Scans all your GraphQL queries and fragments
  • Connects to Shopify's multiple APIs (Storefront, Admin, Customer Account)
  • Generates TypeScript types for every operation
  • Creates a clean export system for easy importing
  • Provides SDK functions for type-safe GraphQL requests

Tech Stack

  • Next.js 15 with App Router
  • Shopify Storefront API, Admin API, Customer Account API
  • GraphQL Code Generator (@graphql-codegen/cli) -> This is crucial for this to work
  • GraphQL Request for client operations

Problem Setup

I had a Next.js 15 e-commerce app with dozens of GraphQL queries spread across multiple files. The queries are now organized by Shopify API type, making it clear which API each query targets:

lib/shopify/queries/
├── admin/
│   └── metaobjects.ts
├── customer-account/
│   └── customer.ts
└── storefront/
    ├── cart.ts
    ├── collection.ts
    ├── menu.ts
    ├── page.ts
    ├── product.ts
    ├── search.ts
    └── ...12 total files

lib/shopify/fragments/
└── storefront/
    ├── product.ts
    ├── image.ts
    ├── cart.ts
    └── seo.ts

lib/shopify/mutations/
└── storefront/
    └── cart.ts

Each query file looked like this:

// The old way - manual types everywhere
export const getProductQuery = `
  query getProduct($handle: String!) {
    product(handle: $handle) {
      id
      title
      handle
      variants(first: 100) {
        edges {
          node {
            id
            price {
              amount
              currencyCode
            }
          }
        }
      }
    }
  }
`;

// Manual type definition - error-prone!
export type Product = {
  id: string;
  title: string;
  handle: string;
  variants: {
    edges: Array<{
      node: {
        id: string;
        price: {
          amount: string; // Is this string or number?
          currencyCode: string;
        };
      };
    }>;
  };
};

The Pain Points

  1. Type Mismatches: Shopify returns amount as a string, but I kept assuming it was a number
  2. Missing Fields: Adding new fields to queries but forgetting to update types
  3. Fragment Management: Complex fragments shared between queries with no type safety
  4. Multiple APIs: Shopify has 3 different GraphQL APIs with different schemas
  5. Maintenance Nightmare: Every schema change required manual type updates

Why This Matters

In a headless commerce setup, your GraphQL queries are the foundation of your entire app. Wrong types lead to:

  • Runtime errors in production
  • Poor developer experience
  • Bugs that are hard to track down
  • Wasted time on manual type maintenance

Approach Exploration

I have considered couple of approaches.

Option 1: Continue Manual Types

Pros: Simple, no new tooling Cons: Error-prone, maintenance heavy, doesn't scale

Option 2: GraphQL Code Generator

Pros: Automatic, always in sync, supports multiple APIs, great DX Cons: Initial setup complexity, requires build step

Option 3: TypeScript + GraphQL Schema Introspection

Pros: No codegen needed Cons: Runtime overhead, complex setup for multiple APIs

Why I Chose GraphQL Code Generator

GraphQL Code Generator is the industry standard because it:

  • Generates types at build time (no runtime cost)
  • Supports complex multi-API setups like Shopify's
  • Creates both types AND SDK functions
  • Has excellent Next.js integration
  • Handles fragments and complex nested structures perfectly

Implementation

Now comes the exciting part – setting up automatic type generation that will transform how you work with GraphQL in your Shopify project. We'll walk through this implementation step-by-step, and by the end, you'll have a system that automatically generates both TypeScript types and SDK functions for all your Shopify API operations.

The process involves several key steps: installing the necessary packages, configuring multiple Shopify API schemas, organizing your queries properly, and setting up the code generation pipeline. Each step builds on the previous one, so we'll take it methodically to ensure everything works perfectly.

Step 1: Install Dependencies

First, let's install the necessary packages for GraphQL code generation:

npm install -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations @graphql-codegen/typescript-graphql-request @shopify/hydrogen-codegen
npm install graphql-request graphql

What each package does:

  • @graphql-codegen/cli: Main code generator CLI
  • @graphql-codegen/typescript: Generates base TypeScript types from GraphQL schema
  • @graphql-codegen/typescript-operations: Generates types for your specific queries/mutations
  • @graphql-codegen/typescript-graphql-request: Creates SDK functions for graphql-request client
  • @shopify/hydrogen-codegen: Provides Shopify's Customer Account API schema access
  • graphql-request: Lightweight GraphQL client (alternative to Apollo)

Step 2: Set Up Environment Variables

Create or update your .env with Shopify API credentials. If you're deploying to Vercel, make sure to add these to your Vercel project's environment variables as well:

# Storefront API (public, for product/collection queries)
# Found in your Shopify headless app - use the "Storefront access token"
SHOPIFY_STOREFRONT_ACCESS_TOKEN=your_storefront_token
NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN=your-store.myshopify.com

# Admin API (private, for metafields/admin operations)
# Found in your Shopify private app - use the "Admin API access token"
SHOPIFY_ADMIN_API_ACCESS_TOKEN=your_admin_token

# Customer Account API (optional, for customer authentication)
# Found in your Shopify headless app - just the shop ID is needed for public schema access
NEXT_PUBLIC_SHOPIFY_SHOP_ID=your_shop_id

Why multiple APIs?

Shopify separates functionality across different GraphQL APIs:

  • Storefront API: Public data (products, collections, cart)
  • Admin API: Private operations (metafield definitions, admin queries)
  • Customer Account API: Customer authentication and orders

For a deeper dive into how these APIs work together and their specific use cases, check out my comprehensive guide: Shopify Core APIs Overview.

Step 3: Create the Code Generator Configuration

This is where the magic happens! We're about to configure a system that will automatically scan all your GraphQL queries, connect to Shopify's schemas, and generate both precise TypeScript types and ready-to-use SDK functions. The best part? Once this is set up, every time you modify a query, the types update automatically – no more manual typing, no more guessing about field structures or nullability.

Create codegen.ts in your project root:

import "dotenv/config";
import type { CodegenConfig } from "@graphql-codegen/cli";
import { getSchema } from "@shopify/hydrogen-codegen";

// Validate required environment variables
if (
  !process.env.SHOPIFY_STOREFRONT_ACCESS_TOKEN ||
  !process.env.NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN ||
  !process.env.SHOPIFY_ADMIN_API_ACCESS_TOKEN
) {
  throw new Error(
    "Missing required Shopify env vars. Check: SHOPIFY_STOREFRONT_ACCESS_TOKEN, NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN, SHOPIFY_ADMIN_API_ACCESS_TOKEN"
  );
}

/**
 * Multi-API GraphQL Codegen Configuration
 *
 * This configuration generates separate TypeScript types for three Shopify APIs:
 * - Storefront API: Most query files organized in storefront/ folder
 * - Customer Account API: Uses Hydrogen's getSchema for proper schema access
 * - Admin API: metaobjects.ts in admin/ folder
 */
const config: CodegenConfig = {
  generates: {
    // Storefront API - Most queries use this
    "./lib/shopify/generated/storefront.ts": {
      schema: {
        [`https://${process.env.NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN}/api/2025-04/graphql.json`]:
          {
            headers: {
              "X-Shopify-Storefront-Access-Token":
                process.env.SHOPIFY_STOREFRONT_ACCESS_TOKEN!,
            },
          },
      },
      documents: [
        "./lib/shopify/queries/storefront/**/*.ts",
        "./lib/shopify/mutations/storefront/**/*.ts",
        "./lib/shopify/fragments/storefront/**/*.ts",
      ],
      plugins: [
        "typescript",
        "typescript-operations",
        "typescript-graphql-request",
      ],
    },

    // Admin API - metafields.ts and metaobjects.ts (definitions only)
    "./lib/shopify/generated/admin.ts": {
      schema: {
        [`https://${process.env.NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN}/admin/api/2024-01/graphql.json`]:
          {
            headers: {
              "X-Shopify-Access-Token":
                process.env.SHOPIFY_ADMIN_API_ACCESS_TOKEN!,
            },
          },
      },
      documents: ["./lib/shopify/queries/admin/**/*.ts"],
      plugins: [
        "typescript",
        "typescript-operations",
        "typescript-graphql-request",
      ],
    },

    // Customer Account API - Uses Hydrogen's getSchema for proper OAuth handling
    "./lib/shopify/generated/customer-account.ts": {
      schema: getSchema("customer-account"),
      documents: ["./lib/shopify/queries/customer-account/**/*.ts"],
      plugins: [
        "typescript",
        "typescript-operations",
        "typescript-graphql-request",
      ],
    },
  },
  config: {
    avoidOptionals: true,
    maybeValue: "T | null",
  },
};

export default config;

Configuration breakdown:

  • generates: Defines what files to create and from which sources
  • schema: Points to Shopify's GraphQL schema endpoints with authentication
  • documents: Array of files containing your GraphQL operations
  • plugins: What to generate (base types, operation types, SDK functions)
  • config.avoidOptionals: Makes nullable fields explicit (T | null instead of T?)

Step 4: Fix Your Query Format

Critical Step: GraphQL Code Generator only recognizes queries with the /* GraphQL */ comment.

Before (doesn't work with codegen):

export const getProductQuery = `
  query getProduct($handle: String!) {
    product(handle: $handle) {
      id
      title
    }
  }
`;

After (works with codegen):

export const getProductQuery = /* GraphQL */ `
  query getProduct($handle: String!) {
    product(handle: $handle) {
      id
      title
    }
  }
`;

Why the comment matters: The /* GraphQL */ comment is a special marker that tells the code generator "this string contains a GraphQL operation." Without it, the generator skips the query entirely.

Step 5: Understanding Generated Type Names

GraphQL codegen automatically creates type names based on your operation names, following a predictable pattern.

Type Naming Rules

GraphQL codegen uses this pattern:

[OperationName] + [OperationType] + ["Variables" for variables]

Examples

For this query:

export const getProductsViaSearchQuery = /* GraphQL */ `
  query SearchProducts($query: String!, $first: Int) {
    search(query: $query, first: $first) {
      totalCount
      edges { node { ... } }
    }
  }
`;

Generated type names:

  • Variables: SearchProductsQueryVariables
  • Result: SearchProductsQuery
  • SDK Function: SearchProducts()

The Pattern Breakdown

  1. Operation Name: SearchProducts (from your GraphQL operation)
  2. + Operation Type: Query (automatically detected and appended)
  3. + "Variables": Variables (for input variables)

More examples:

query getProduct       →  GetProductQuery + GetProductQueryVariables
query predictiveSearch →  PredictiveSearchQuery + PredictiveSearchQueryVariables
mutation addToCart     →  AddToCartMutation + AddToCartMutationVariables

Key Insights:

The JavaScript variable name like getProductsViaSearchQuery is completely ignored by the code generator. Only the GraphQL operation name like SearchProducts matters for type generation. The system automatically appends "Query", "Mutation", or "Subscription" to create the final type names, so you should never include the operation type in your operation name to avoid redundancy like SearchProductsQueryQuery.

Best Practice:

// Good - clear operation name
query SearchProducts { ... }  →  SearchProductsQuery

// Bad - redundant naming
query SearchProductsQuery { ... }  →  SearchProductsQueryQuery

Step 6: Structure Your Query Files

Organize your queries with proper fragments for reusability:

// lib/shopify/fragments/product.ts
import imageFragment from "./image";
import seoFragment from "./seo";

const productFragment = /* GraphQL */ `
  fragment product on Product {
    id
    handle
    title
    description
    availableForSale
    featuredImage {
      ...image
    }
    variants(first: 100) {
      edges {
        node {
          id
          title
          price {
            amount
            currencyCode
          }
          selectedOptions {
            name
            value
          }
        }
      }
    }
    seo {
      ...seo
    }
  }
  ${imageFragment}
  ${seoFragment}
`;

export default productFragment;
// lib/shopify/queries/product.ts
import productFragment from "../fragments/product";

export const getProductQuery = /* GraphQL */ `
  query getProduct($handle: String!) {
    product(handle: $handle) {
      ...product
    }
  }
  ${productFragment}
`;

export const getProductsQuery = /* GraphQL */ `
  query getProducts($first: Int, $query: String, $sortKey: ProductSortKeys) {
    products(first: $first, query: $query, sortKey: $sortKey) {
      edges {
        node {
          ...product
        }
      }
      pageInfo {
        hasNextPage
        endCursor
      }
    }
  }
  ${productFragment}
`;

Fragment benefits:

  • Reusability: Define common field sets once, use everywhere
  • Consistency: Ensures the same fields are fetched across different queries
  • Maintainability: Update fragment once, all queries get updated
  • Type Safety: Generated types automatically include fragment fields

Step 7: Add Package.json Script

Add the codegen script to your package.json:

{
  "scripts": {
    "codegen": "graphql-codegen --config codegen.ts",
    "codegen:watch": "graphql-codegen --config codegen.ts --watch"
  }
}

Step 8: Run Code Generation

Execute the type generation:

npm run codegen

What happens during generation:

  1. Codegen reads your configuration
  2. Fetches GraphQL schemas from Shopify APIs
  3. Scans all files in your documents arrays
  4. Extracts GraphQL operations (queries, mutations, fragments)
  5. Matches operations against schemas
  6. Generates TypeScript types for each operation
  7. Creates SDK functions for making requests

You should see output like:

[SUCCESS] Generate to ./lib/shopify/generated/storefront.ts
[SUCCESS] Generate to ./lib/shopify/generated/admin.ts

Step 9: Create a Clean Export System

Create lib/shopify/types/index.ts to organize all your types:

// Clean exports for all Shopify API types
// This file combines both generated types and manual types for easy importing

// Re-export generated types from Storefront API
export type {
  // Operation types
  GetProductQuery,
  GetProductQueryVariables,
  GetProductsQuery,
  GetProductsQueryVariables,
  PredictiveSearchQuery,
  PredictiveSearchQueryVariables,

  // Schema types
  Product as GeneratedProduct,
  ProductVariant as GeneratedProductVariant,
  MoneyV2 as GeneratedMoney,
  Image as GeneratedImage,
  Collection as GeneratedCollection,
} from "../generated/storefront";

// Re-export generated types from Admin API
export type {
  GetMetafieldsQuery,
  GetMetafieldsQueryVariables,
  MetafieldDefinition as GeneratedMetafieldDefinition,
} from "../generated/admin";

// Re-export any remaining manual types that extend generated ones
export type {
  // Custom types that reshape or extend generated types
  Cart,
  CartItem,
  Product,
  Collection,
  // ... other manual types you still need
} from "../types";

// Convenient type aliases for common use cases
import type { Product, Cart, Collection } from "../types";
export type ProductWithVariants = Product;
export type CartWithItems = Cart;
export type CollectionWithProducts = Collection;

// Operation result types for GraphQL requests
export type StorefrontQueryResult<T> = {
  data: T;
  errors?: Array<{
    message: string;
    locations?: Array<{
      line: number;
      column: number;
    }>;
    path?: Array<string | number>;
  }>;
  extensions?: Record<string, any>;
};

FAQ: Troubleshooting Common Issues

Common Mistakes and Solutions

Missing /* GraphQL */ Comment

Error:

No operations found in documents

What happened: Without the special comment, codegen couldn't identify which strings contained GraphQL operations.

Fix: Add /* GraphQL */ before every query template literal.

Wrong API Configuration

Error:

Field 'customer' doesn't exist on type 'QueryRoot'

What happened: Customer queries were in the Storefront API configuration instead of Customer Account API.

Fix: Move customer-related queries to the correct API configuration in codegen.ts.

Fragment Import Issues

Error:

Unknown fragment "product"

What happened: Fragment wasn't properly imported or the template literal composition was incorrect.

Fix:

// Correct fragment usage
export const getProductQuery = /* GraphQL */ `
  query getProduct($handle: String!) {
    product(handle: $handle) {
      ...product
    }
  }
  ${productFragment}  // ← Fragment must be composed here
`;

Environment Variables Not Loaded

Error:

Missing required Shopify env vars

What happened: The dotenv/config import in codegen.ts couldn't find environment variables.

Fix: Ensure your .env.local file is in the project root and contains all required variables.

Debugging Commands

Verify Generated Types

# Check what types were actually generated
grep -E "export type.*Query" lib/shopify/generated/storefront.ts | head -10

Test Type Compilation

# Verify TypeScript compilation works
npx tsc --noEmit lib/shopify/types/index.ts

Query Extraction Checklist

If a query isn't generating types, ensure:

  1. File is listed in codegen.ts documents array
  2. Query has /* GraphQL */ comment
  3. Query syntax is valid GraphQL
  4. Operation has a name (query getProduct not just query)

Final Working Version

Using the Generated Types

Here's how to use your generated types in a Next.js component:

// components/ProductCard.tsx
import { getSdk } from '@/lib/shopify/generated/storefront';
import { GraphQLClient } from 'graphql-request';
import type {
  GetProductQuery,
  GetProductQueryVariables
} from '@/lib/shopify/types';

// Create GraphQL client
const client = new GraphQLClient(
  `https://${process.env.NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN}/api/2025-04/graphql.json`,
  {
    headers: {
      'X-Shopify-Storefront-Access-Token': process.env.SHOPIFY_STOREFRONT_ACCESS_TOKEN!,
    },
  }
);

// Get SDK with full type safety
const sdk = getSdk(client);

export async function ProductCard({ handle }: { handle: string }) {
  // Fully typed variables
  const variables: GetProductQueryVariables = { handle };

  // Fully typed response
  const response: GetProductQuery = await sdk.getProduct(variables);

  const product = response.product;

  if (!product) {
    return <div>Product not found</div>;
  }

  return (
    <div>
      <h2>{product.title}</h2>
      <p>{product.description}</p>
      {product.featuredImage && (
        <img
          src={product.featuredImage.url}
          alt={product.featuredImage.altText || product.title}
          width={product.featuredImage.width || 300}
          height={product.featuredImage.height || 300}
        />
      )}
      <div>
        ${product.priceRange.minVariantPrice.amount} {product.priceRange.minVariantPrice.currencyCode}
      </div>
    </div>
  );
}

Project Structure Overview

Your final project structure should look like:

├── codegen.ts                          # Code generator configuration
├── lib/shopify/
│   ├── queries/                        # GraphQL queries
│   │   ├── product.ts                  # Product queries
│   │   ├── cart.ts                     # Cart operations
│   │   ├── search.ts                   # Search functionality
│   │   └── ...
│   ├── fragments/                      # Reusable GraphQL fragments
│   │   ├── product.ts                  # Product fields
│   │   ├── image.ts                    # Image fields
│   │   └── ...
│   ├── generated/                      # Auto-generated files (don't edit!)
│   │   ├── storefront.ts               # Storefront API types
│   │   ├── admin.ts                    # Admin API types
│   │   └── customer-account.ts         # Customer API types
│   ├── types/
│   │   ├── index.ts                    # Clean exports
│   │   └── types.ts                    # Legacy manual types
│   └── utils/
│       └── clients.ts                  # GraphQL client setup
└── package.json                        # Scripts for codegen

Development Workflow

  1. Write or modify a GraphQL query with /* GraphQL */ comment
  2. Run code generation: npm run codegen
  3. Import generated types in your components
  4. Use the SDK functions for type-safe requests
  5. Repeat as you add new features

Key benefits of this approach:

  • Always in sync: Types match your actual queries
  • Compile-time safety: Catch errors before runtime
  • Great DX: Autocomplete and IntelliSense for all fields
  • SDK functions: No need to write GraphQL request boilerplate
  • Multi-API support: Handle Shopify's complex API structure

Optional Improvements & Best Practices

1. Automated Code Generation

Add codegen to your build process:

{
  "scripts": {
    "build": "npm run codegen && next build",
    "dev": "npm run codegen && next dev"
  }
}

2. Git Integration

Add generated files to .gitignore or commit them:

# Option 1: Ignore generated files (regenerate on each deploy)
lib/shopify/generated/

# Option 2: Commit generated files (faster deploys, easier debugging)
# Keep generated files in git

Tip: Committing generated files makes deployments faster but creates larger diffs. Choose based on your team's preference.

3. Type-Safe Client Wrapper

For the Customer Account API, create a specialized client that handles authentication properly:

// lib/shopify/utils/customer-account-sdk.ts
import { GraphQLClient } from "graphql-request";
import { getSdk } from "../generated/customer-account";
import { SHOPIFY_CUSTOMER_ACCOUNT_API_ENDPOINT } from "lib/constants";

/**
 * Creates Customer Account API SDK client with authentication
 */
export function createCustomerAccountSdk(accessToken: string) {
  // Create a client with custom fetch that handles token formatting
  const client = new GraphQLClient(SHOPIFY_CUSTOMER_ACCOUNT_API_ENDPOINT, {
    fetch: async (url: RequestInfo | URL, init?: RequestInit) => {
      const { formatAccessToken } = await import("lib/auth/session");
      const formattedToken = formatAccessToken(accessToken);

      return fetch(url, {
        ...init,
        headers: {
          ...init?.headers,
          "Content-Type": "application/json",
          Authorization: formattedToken, // Customer Account API expects token directly, no "Bearer" prefix
        },
      });
    },
  });

  return getSdk(client);
}

// Export for type inference helpers
export type CustomerAccountSdk = ReturnType<typeof createCustomerAccountSdk>;

4. Cache Integration

Integrate with Next.js caching:

// lib/shopify/cached-client.ts
import { unstable_cache } from "next/cache";
import { shopifyClient } from "./client";

export const getCachedProduct = unstable_cache(
  async (handle: string) => {
    return shopifyClient.getProduct({ handle });
  },
  ["product"],
  { revalidate: 3600 } // 1 hour cache
);

5. Development Scripts

Add helpful development scripts:

{
  "scripts": {
    "codegen": "graphql-codegen --config codegen.ts",
    "codegen:watch": "graphql-codegen --config codegen.ts --watch",
    "codegen:check": "graphql-codegen --config codegen.ts --check",
    "types:check": "tsc --noEmit"
  }
}

Summary

By implementing GraphQL Code Generation, you've transformed your Shopify headless storefront from error-prone manual types to a robust, automatically-maintained type system.

What you've gained:

  • Type Safety: Compile-time errors instead of runtime surprises
  • Always In Sync: Types automatically match your queries
  • Better DX: Full autocomplete and IntelliSense support
  • SDK Functions: No more manual GraphQL request boilerplate
  • Multi-API Support: Clean handling of Shopify's complex API structure
  • Maintainability: Add new queries without manual type work

The key insight: The simple /* GraphQL */ comment is what unlocks automatic type generation. This small change eliminates hours of manual type maintenance and prevents entire categories of bugs.

Your codebase is now more reliable, maintainable, and developer-friendly. As you add new features, the type system will grow automatically with your queries, keeping everything in perfect sync.

Thanks, Matija

0

Frequently Asked Questions

Comments

Enjoyed this article?
Subscribe to my newsletter for more insights and tutorials.
Matija Žiberna
Matija Žiberna
Full-stack developer, co-founder

I'm Matija Žiberna, a self-taught full-stack developer and co-founder passionate about building products, writing clean code, and figuring out how to turn ideas into businesses. I write about web development with Next.js, lessons from entrepreneurship, and the journey of learning by doing. My goal is to provide value through code—whether it's through tools, content, or real-world software.

You might be interested in