Tag: wordpress optimization

  • Automate GST Reports for WooCommerce Using Google Sheets + Apps Script

    Step-by-step automation — from WooCommerce to monthly GST report, no manual exports.


    What This Solves

    If you manage a WooCommerce store on WordPress, this guide will help you:

    • Extract completed order data from your WordPress site
    • Sync it into Google Sheets via Apps Script
    • Auto-generate a GST-compliant monthly report
    • Email it — as a downloadable Google Sheet — every month

    No plugins. No third-party tools. Full control.


    Step 1: Create export_orders.php in Your WooCommerce Site

    What It Does

    This script connects directly to your WordPress backend using core WooCommerce functions to fetch order data in CSV format.

    Where to Place It

    • Upload this to your theme root or a protected folder like:
    /wp-content/export/export_orders.php

    Access via:

    https://yourdomain.com/wp-content/export-secure/export_orders.php?token=your_secure_token_here

    Ensure it’s not publicly browsable without restrictions (add basic auth or token check in production).


    export_orders.php

    <?php
    // CONFIGURE YOUR SECRET TOKEN BELOW
    define('EXPORT_TOKEN', 'your_secure_token_here');
    
    // Block unauthorized access
    if (!isset($_GET['token']) || $_GET['token'] !== EXPORT_TOKEN) {
        http_response_code(403);
        exit('Unauthorized access');
    }
    
    // Load WordPress core
    require_once($_SERVER['DOCUMENT_ROOT'] . '/wp-load.php');
    
    // Ensure WooCommerce is active
    if (!class_exists('WooCommerce')) {
        exit('WooCommerce is not active');
    }
    
    // Fetch all orders
    $args = array(
        'post_type'      => 'shop_order',
        'post_status'    => array_keys(wc_get_order_statuses()),
        'posts_per_page' => -1,
        'orderby'        => 'date',
        'order'          => 'DESC',
    );
    
    $orders = get_posts($args);
    $csv_data = [];
    
    // Process each order
    foreach ($orders as $post) {
        $order = wc_get_order($post->ID);
        $data = $order->get_data();
    
        // Basic order info
        $row = [
            'Order_ID'         => $order->get_id(),
            'Order_Status'     => $order->get_status(),
            'Payment_Method'   => $data['payment_method_title'] ?? 'NA',
            'Total'            => $data['total'] ?? 0,
            'Tax_Total'        => $data['total_tax'] ?? 0,
            'Shipping_Total'   => $data['shipping_total'] ?? 0,
            'COD_Charges'      => get_post_meta($order->get_id(), '_cod_charges', true) ?: 0,
            'Invoice_Number'   => get_post_meta($order->get_id(), '_wcpdf_invoice_number', true) ?: 'NA',
            'Invoice_Date'     => get_post_meta($order->get_id(), '_wcpdf_invoice_date_formatted', true) ?: 'NA',
            'Date_Created'     => $data['date_created']->date('Y-m-d H:i:s'),
            'Date_Modified'    => $data['date_modified']->date('Y-m-d H:i:s'),
        ];
    
        // Flatten billing info
        foreach ($data['billing'] as $key => $val) {
            $row['Billing_' . ucfirst($key)] = $val;
        }
    
        // Flatten shipping info
        foreach ($data['shipping'] as $key => $val) {
            $row['Shipping_' . ucfirst($key)] = $val;
        }
    
        // Line items
        foreach ($order->get_items() as $item) {
            if (!is_a($item, 'WC_Order_Item_Product')) continue;
    
            $product = $item->get_product();
            if (!$product) continue;
    
            $product_row = $row;
            $product_row['Product_Name'] = $product->get_name();
            $product_row['Quantity']     = $item->get_quantity();
            $product_row['Size']         = $product->get_attribute('pa_size');
            $product_row['Color']        = $product->get_attribute('pa_color');
            $product_row['Rate']         = $product->get_price();
    
            // Sanitize all data
            foreach ($product_row as $k => $v) {
                $product_row[$k] = is_scalar($v) ? $v : json_encode($v);
            }
    
            $csv_data[] = $product_row;
        }
    }
    
    // Serve CSV in memory
    if (!empty($csv_data)) {
        header('Content-Type: text/csv');
        header('Content-Disposition: attachment; filename="exported_orders.csv"');
    
        $output = fopen('php://output', 'w');
        fputcsv($output, array_keys($csv_data[0]));
        foreach ($csv_data as $row) {
            fputcsv($output, $row);
        }
        fclose($output);
        exit;
    } else {
        echo "No orders to export.";
    }
    

    Add authentication if this is accessible from the web (e.g., API key via URL parameter or Basic Auth header).


    Step 2: Connect Google Apps Script

    1. Open a Google Sheet
    2. Go to Extensions → Apps Script
    3. Paste the updateData() function to pull this CSV and store it in a sheet called Data
    4. Follow with generateSalesReport() and generateGSTReport() functions (see earlier messages)

    Update the url in updateData() like this:

    var url = "https://yourdomain.com/wp-content/export-secure/export_orders.php?token=your_secure_token_here"

    1. Import Orders from Your Endpoint

    Use this function to:

    • Fetch live order data via UrlFetchApp
    • Parse CSV
    • Filter for only "completed" orders
    • Overwrite your "Data" sheet with filtered results
    function updateData() {
      const url = "https://yourdomain.com/wp-content/export-secure/export_orders.php?token=your_secure_token_here";
      const response = UrlFetchApp.fetch(url);
      const csvData = Utilities.parseCsv(response.getContentText());
    
      const completed = csvData.filter((row, i) => i === 0 || row[2] === 'completed');
      const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Data") || SpreadsheetApp.getActiveSpreadsheet().insertSheet("Data");
    
      sheet.clear();
      sheet.getRange(1, 1, completed.length, completed[0].length).setValues(completed);
    }

    2. Create a “Sales Report” Sheet

    This formats your filtered data, assigns serial numbers (Sno), and builds a structured report with invoice numbers like:

    function generateSalesReport() {
      const ss = SpreadsheetApp.getActiveSpreadsheet();
      const dataSheet = ss.getSheetByName("Data");
      const salesSheet = ss.getSheetByName("Sales Report") || ss.insertSheet("Sales Report");
    
      salesSheet.clear();
      const data = dataSheet.getDataRange().getValues();
    
      // Sort and structure the report
      data.sort((a, b) => (a[0] - b[0]) || new Date(b[62]) - new Date(a[62]));
    
      const header = data[0].concat(["Sno", "Invoice Date", "Invoice Number"]);
      const salesData = [];
      const orderMap = {};
    
      for (let i = 1; i < data.length; i++) {
        const row = data[i];
        const id = row[0];
        const date = new Date(row[62]);
        const sno = orderMap[id] || Object.keys(orderMap).length + 1;
        orderMap[id] = sno;
    
        const invoiceNo = Utilities.formatDate(date, Session.getScriptTimeZone(), "yy-MM") + "-" + id + "-" + ("000000" + sno).slice(-7);
        salesData.push(row.concat([sno, date, invoiceNo]));
      }
    
      salesSheet.getRange(1, 1, 1, header.length).setValues([header]);
      salesSheet.getRange(2, 1, salesData.length, header.length).setValues(salesData);
    }

    3. Generate the GST Report for Previous Month

    This function:

    • Filters last month’s sales
    • Groups by order ID
    • Computes invoice-level GST
    • Creates a new sheet file in Drive
    • Emails it as an attachment
    function generateGSTReport() {
      updateData();
      generateSalesReport();
    
      const salesSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sales Report");
      const data = salesSheet.getDataRange().getValues();
    
      const today = new Date();
      const prevMonthStart = new Date(today.getFullYear(), today.getMonth() - 1, 1);
      const prevMonthEnd = new Date(today.getFullYear(), today.getMonth(), 0, 23, 59, 59, 999);
    
      const filtered = data.filter(row => new Date(row[69]) >= prevMonthStart && new Date(row[69]) <= prevMonthEnd);
      const orders = {};
    
      filtered.forEach(row => {
        const id = row[0];
        const total = parseFloat(row[11]);
        const tax = parseFloat(row[12]);
        const name = row[40] + ' ' + row[41];
        const state = row[57];
    
        if (!orders[id]) {
          orders[id] = {
            orderId: id,
            invoiceDate: row[69],
            invoiceNumber: row[70],
            customerName: name,
            totalSum: 0,
            totalTaxSum: 0,
            totalCount: 0,
            shippingState: state,
          };
        }
    
        orders[id].totalSum += total;
        orders[id].totalTaxSum += tax;
        orders[id].totalCount++;
      });
    
      const rows = [["Order ID", "Invoice Date", "Invoice Number", "Customer Name", "Total Amount", "GST Amount", "Shipping State", "Source State", "GST Rate", "GSTN"]];
      Object.values(orders).forEach(o => {
        rows.push([
          o.orderId,
          o.invoiceDate,
          o.invoiceNumber,
          o.customerName,
          (o.totalSum / o.totalCount).toFixed(2),
          (o.totalTaxSum / o.totalCount).toFixed(2),
          o.shippingState,
          "MH",
          "12%",
          "27ABICS6682G1ZU"
        ]);
      });
    
      const label = Utilities.formatDate(prevMonthStart, Session.getScriptTimeZone(), "MMM-yy");
      const newSheet = SpreadsheetApp.create("GST Report_" + label);
      newSheet.getSheets()[0].getRange(1, 1, rows.length, rows[0].length).setValues(rows);
    
      const folder = DriveApp.getFolderById("your-folder-id");
      const file = DriveApp.getFileById(newSheet.getId());
      folder.addFile(file);
      DriveApp.getRootFolder().removeFile(file);
    
      const total = Object.values(orders).reduce((a, o) => a + parseFloat(o.totalSum / o.totalCount), 0).toFixed(2);
      const gst = Object.values(orders).reduce((a, o) => a + parseFloat(o.totalTaxSum / o.totalCount), 0).toFixed(2);
    
      GmailApp.sendEmail("you@example.com", `GST Report - ${label}`, `Please find attached the GST report for { enter your GST#}.\nTotal Orders: ${rows.length - 1}\nSales: ₹${total}\nGST: ₹${gst}\n${newSheet.getUrl()}`, {
        attachments: [file]
      });
    }

    Folder Setup Summary

    FileLocationPurpose
    export_orders.php/wp-content/export/Outputs WooCommerce orders in CSV format
    Google SheetAnyStores raw data + reports
    Apps ScriptInside Google SheetAutomates pull, processes GST, emails output
    GST Report FolderGoogle DriveArchives the monthly reports

    Example Output (Final Email)

    Subject: GST Report – Apr 2025
    Body:

    Please find attached the GST report for GSTN:xxxxxxxxxxxx.
    
    Total Orders: 42  
    Total Sales: ₹3,24,870.00  
    GST Collected: ₹38,090.00  
    Report Link: [view on Google Sheets]

    Warning: We’ve prepared this script for our internal reporting and hence, you may have adjust references to columns.

    Feel free to share your queries in comment.

  • Can WordPress Be Faster Than Shopify? Yes — If You Know What You’re Doing.

    Shopify is built for simplicity. It’s hosted, optimized, and largely hands-off.
    But WordPress? It’s raw power. You get full control — but that means speed is your responsibility.

    If you configure WordPress right, it can be faster, leaner, and more flexible than Shopify. But it won’t happen by accident.

    Here’s a no-BS breakdown of what makes WordPress fast — and how to outpace Shopify at its own game.


    1. Hosting: Where Speed Begins

    Shopify runs on optimized infrastructure. You need to match that firepower.

    • Choose LiteSpeed or NGINX-based hosts like Hostinger, Cloudways, or Rocket.net
    • Go for Managed WordPress Hosting if you don’t want to tweak configs yourself
    • Avoid oversold shared hosting — it’s speed poison

    2. Database Hygiene

    Shopify manages your DB behind the scenes. WordPress? You need to keep it lean.

    • Delete old post revisions, transients, spam comments
    • Use tools like WP-Optimize or Advanced Database Cleaner
    • Run periodic optimization queries or cron jobs

    A bloated wp_posts table is one of the top killers of admin speed.


    3. Caching: The Equalizer

    Without caching, WordPress hits the database for every request. Don’t let it.

    • Page caching: WP Rocket, LiteSpeed Cache, or W3 Total Cache
    • Browser caching: Enable via .htaccess or plugin
    • Object caching: Use Redis or Memcached if supported

    Shopify caches by default. WordPress can, but only if you configure it.


    4. Themes That Don’t Suck

    Your theme matters more than you think.

    • Use themes like GeneratePress, Blocksy, or Kadence
    • Avoid multipurpose themes with 10MB of JS
    • Run GTmetrix or PageSpeed on the demo before you install

    Don’t fall for pretty — fall for fast.


    5. Content Optimization: Images First

    Images are usually the heaviest thing on any site.

    • Use WebP, resize before upload, compress everything
    • Lazy load images below the fold
    • Avoid sliders and background videos unless they’re critical to your UX

    Shopify compresses images automatically. You’ll have to do this on WordPress — but you can do it better.


    6. Use a CDN

    Static assets shouldn’t fly across continents.

    • Use Cloudflare, BunnyCDN, or Fastly
    • Set cache rules smartly — don’t cache dynamic cart or checkout pages
    • Serve fonts, images, and scripts from edge servers

    This is where WordPress can outscale Shopify — you choose your stack.


    7. Technical Wins Most People Ignore

    • Minify & combine CSS/JS
    • Enable GZIP or Brotli compression
    • Use latest PHP (8.2+)
    • Set cache-control and expires headers properly
    • Remove unused plugins and themes — don’t just deactivate

    Speed is cumulative. Every millisecond matters.


    8. Server-Side Power Moves (for advanced users)

    • Enable OPcache
    • Run scheduled DB maintenance via cron
    • Monitor TTFB (Time to First Byte) with tools like GTmetrix or New Relic
    • Avoid WooCommerce unless you need eCommerce. It’s heavier than pure WP.

    Don’t Expect WordPress to Be Fast by Default

    It isn’t. That’s the trade-off for power and control.
    But when configured right, WordPress can outperform Shopify — especially for blogs, content-heavy sites, or stores that need flexibility.


    TL;DR – WordPress Can Be Faster Than Shopify If:

    • Your hosting is solid
    • You cache everything smartly
    • You don’t overload with plugins
    • You optimize your images and scripts
    • You stay in control of what loads and when

    Would love to hear your view in comments.

  • Auto-Fetch Shopify Orders into Google Sheets Using Apps Script

    Sync orders with pagination, filters & timestamp — no plugins.


    What You’ll Need

    • Shopify access token (shpat_...)
    • Your store name (e.g., yourstore)
    • A Google Sheet

    Step-by-Step Instructions

    1. Open Your Google Sheet

    Go to Extensions → Apps Script


    2. Paste the Script

    Replace credentials:

    const SHOPIFY_STORE_NAME = 'yourstore';
    const SHOPIFY_ACCESS_TOKEN = 'shpat_xxxxxxxxxxxxxxxx';

    Then paste the full script below that handles:

    • Sheet setup
    • Pagination (page_info)
    • Timestamp-based incremental sync
    • Field mapping
    const SHOPIFY_STORE_NAME = 'your-store-name';  // Replace with your Shopify store name
    const SHOPIFY_ACCESS_TOKEN = 'shpat_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';  // Replace with your access token
    
    function fetchShopifyOrders() {
      const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
      
      let ordersSheet = spreadsheet.getSheetByName("Orders") || spreadsheet.insertSheet("Orders");
      let lastFetchSheet = spreadsheet.getSheetByName("lastfetchtime") || spreadsheet.insertSheet("lastfetchtime");
    
      let lastFetchTime = lastFetchSheet.getRange('A1').getValue();
      if (isNaN(new Date(lastFetchTime).getTime())) lastFetchTime = null;
    
      let url = `https://${SHOPIFY_STORE_NAME}.myshopify.com/admin/api/2023-01/orders.json?limit=50&order=processed_at asc`;
      url += lastFetchTime ? `&created_at_min=${new Date(lastFetchTime).toISOString()}&status=any` : `&status=any`;
    
      const options = {
        method: 'get',
        headers: {
          "Content-Type": "application/json",
          "X-Shopify-Access-Token": SHOPIFY_ACCESS_TOKEN
        }
      };
    
      let pageInfo = null;
      let ordersFetched = 0;
    
      if (!lastFetchTime) {
        const headers = ["Order Name", "Date", "Status", "Invoice Value", "GST", "Shipping Charges", "Shipping State", "Fulfillment Status", "Fulfillment Date", "Refunded", "Delivery Status", "AWB/Tracking Number", "Customer Name", "Customer Address", "Product Details"];
        ordersSheet.clear();
        ordersSheet.appendRow(headers);
      }
    
      do {
        let fetchUrl = pageInfo ? `https://${SHOPIFY_STORE_NAME}.myshopify.com/admin/api/2023-01/orders.json?limit=50&page_info=${pageInfo}` : url;
    
        const response = UrlFetchApp.fetch(fetchUrl, options);
        const data = JSON.parse(response.getContentText());
        const orders = data.orders;
    
        if (!orders.length) break;
    
        orders.forEach(order => {
          const rowData = [
            order.name,
            order.created_at,
            order.financial_status,
            order.total_price,
            order.total_tax,
            order.shipping_lines.length > 0 ? order.shipping_lines[0].price : 0,
            order.shipping_address?.province || 'Unknown',
            order.fulfillment_status || 'Unfulfilled',
            order.fulfillments[0]?.created_at || 'Pending',
            order.refunds.length > 0 ? 'Yes' : 'No',
            order.fulfillments.length > 0 ? 'Shipped' : 'Pending',
            order.fulfillments[0]?.tracking_numbers[0] || 'Pending',
            order.shipping_address?.name || 'Unknown',
            order.shipping_address ? `${order.shipping_address.address1}, ${order.shipping_address.city}, ${order.shipping_address.province} - ${order.shipping_address.zip}, ${order.shipping_address.country}` : 'Unknown',
            order.line_items.map(item => `${item.title} (Qty: ${item.quantity})`).join(', ')
          ];
          ordersSheet.appendRow(rowData);
        });
    
        Utilities.sleep(1000);
    
        const linkHeader = response.getHeaders()['Link'];
        pageInfo = linkHeader?.includes('rel="next"') ? (linkHeader.match(/<([^>]+)>;\s*rel="next"/)?.[1].split('page_info=')[1]) : null;
        ordersFetched += orders.length;
    
      } while (pageInfo);
    
      lastFetchSheet.getRange('A1').setValue(new Date());
    }

    3. Run the Script Once

    • Select fetchShopifyOrders
    • Click ▶️ Run
    • Approve OAuth access
    • Orders appear in “Orders” sheet
    • Timestamp is stored in “lastfetchtime”

    4. What It Pulls

    Each row includes:

    • Order ID & Date
    • Payment status & invoice value
    • GST & shipping charges
    • Fulfillment status + date
    • Tracking number
    • Customer name & address
    • Product summary (Product A (Qty: 2))

    5. How It Works

    • First run → pulls all orders
    • Future runs → pulls only new orders
    • Handles Shopify pagination with page_info
    • Sleeps between requests to stay within API limits
    • Writes last fetch timestamp to avoid duplicates

    Optional Upgrades

    Set time-driven trigger (daily/weekly)

    Add error logging or Slack/email alerts

    Link to dashboards or Data Studio


    Security Tip

    Never share your shpat_ key publicly.
    Use private sheets or store secrets in PropertiesService.

  • WP Speed Tip #10 – Lazy Load Like a Pro (And Know When It’s Not Working)

    Why It Matters

    Lazy loading delays the loading of images until they’re visible on screen.
    It’s a no-brainer for speed — especially for image-heavy pages.

    But here’s the truth:

    • Just having loading="lazy" in HTML doesn’t guarantee it works.
    • Some themes/plugins load images via background CSS or JS — bypassing lazy load.
    • Improper lazy load can break LCP (Largest Contentful Paint) — killing Core Web Vitals.

    The Fix: Confirm Lazy Load Actually Works

    Step 1: Check Your Source Code

    Open your site → right-click → View Page Source

    Look for this inside your <img> tags:

    <img src="image.webp" loading="lazy" alt="..." />

    If it’s missing, your theme or plugin isn’t enabling native lazy loading.


    Step 2: Use Browser DevTools (Real Check)

    1. Right-click → Inspect → Network tab
    2. Reload page with throttling (3G or “Slow 3G”)
    3. Scroll slowly — watch image requests appear as you scroll

    If images load before they appear, lazy loading is not working.


    How to Force Lazy Load (If Missing)

    Option 1: Use Native WordPress Lazy Loading (Enabled by default in WP 5.5+)

    Just make sure your theme outputs standard <img> tags — no weird JS-based rendering.

    Option 2: Manually Add loading="lazy"

    In your theme files (loop, templates, etc.):

    <img src="<?php echo $image_url; ?>" loading="lazy" alt="<?php echo $alt_text; ?>">

    Option 3: Use a Lightweight Plugin

    • Native Lazyload (by Google)
    • LiteSpeed Cache (built-in lazyload + fine-tuning)
    • Perfmatters (paid, but very effective)

    When Not to Use Lazy Load

    • On hero images above the fold — they delay LCP
    • For logo or brand trust icons
    • Background images loaded via CSS: background-image (not lazy-loaded by default)

    Pro Tip:

    Use the fetchpriority attribute for hero/primary images:

    <img src="banner.webp" fetchpriority="high" alt="Hero Image">

    Real-World Impact

    After replacing JS-based image lazy load with native lazy loading, CLS dropped by 30% and LCP improved from 3.4s to 1.7s.


    That wraps up the first 10 tips. Next series coming:
    “Real-World WordPress Speed Makeovers (Case Studies + Fixes)”

  • WP Speed Tip #9 – Clean Up Unused Plugins and Themes (Don’t Just Deactivate Them)

    Why It Matters

    Many WordPress users think deactivating a plugin is enough.

    It’s not.

    Even deactivated plugins:

    • Stay in your file system
    • Show up in plugin scans
    • Are vulnerable to exploits if not updated
    • Slow down admin functions like plugin checks and updates

    Same goes for themes: that old bloated theme you “tested once” is still sitting in /wp-content/themes/.


    What Happens If You Don’t Clean Up:

    • You get bloated update requests on every admin load
    • Security risk increases (unused but exposed code = attack surface)
    • Backup size increases unnecessarily
    • Site scans take longer
    • You forget what’s active and what’s not

    The Fix: Delete, Don’t Deactivate

    Go to:

    Dashboard → Plugins → Installed Plugins

    1. Deactivate anything you’re not using
    2. Then click “Delete”
    3. Repeat for all except what’s active and critical

    For themes:

    Dashboard → Appearance → Themes

    • Keep only:
      • Your active theme (e.g. GeneratePress/Blocksy)
      • One fallback default (e.g. Twenty Twenty-Four)
    • Delete the rest

    Pro Tip: Delete Sample Content Too

    • “Hello World” post
    • “Sample Page”
    • Default comment
    • Unused image sizes in Media Library
    • Unused shortcodes or plugin junk left behind (like from Elementor)

    Bonus: Use This WP-CLI Command (for advanced users)

    wp plugin delete plugin-slug
    wp theme delete theme-slug

    Fastest way to clean up large installs.


    Real-World Impact

    A client site with 21 deactivated plugins dropped its backup file size from 340MB to 95MB just by deleting unused ones.


    Bookmark this tip. Next:
    “Tip #10 – Lazy Load Like a Pro (and Know When It’s Not Working)”

  • Setting Up Your Playground (Your First React App)

    Quick Recap from the previous post

    In our last post, you discovered why React was created — to fix the messiness of pure JavaScript by making UI building faster, smarter, and reusable through components and the Virtual DOM.

    Now it’s time to get your hands dirty. In this post, we’ll guide you step-by-step in setting up your React playground so you can start building real things.


    Why “Playground”?

    Think of your React setup like a digital art studio:

    • You install the tools (like brushes and colors)
    • You create a canvas (React project)
    • And from there, you can make anything — websites, apps, games, or tools

    Tools You’ll Need

    ToolWhat It Does
    Node.jsAllows you to run JavaScript outside a browser
    NPM or YarnLets you install packages like React
    VS CodeThe code editor where you write your React magic
    TerminalLike a command-line remote control for your studio

    Step 1: Install Node.js

    Go to https://nodejs.org and download the LTS version. This will install both Node and NPM.

    Open PowerShell on Windows or Terminal app on Mac/Linux To verify:

    node -v
    # This checks if Node.js is installed, and shows the version. Example: v20.5.1
    
    npm -v
    # This checks if npm (Node Package Manager) is installed, and shows its version. Example: 10.2.0

    Step 2: Create a New React App

    Use Vite (fast and modern) or Create React App (classic and beginner-safe).

    Option A: Using Vite (Recommended)

    npm create vite@latest my-react-app
    # This creates a new React project named "my-react-app" using Vite.
    # You'll be asked to pick a framework — choose "React" and then "JavaScript" (not TypeScript, for now).
    # Use npx vite@latest
    # As a fallback if 'npm create vite@latest' doesn't work
    
    cd my-react-app
    # This moves you into the project folder you just created.
    
    npm install
    # This installs all the required packages (React, Vite, etc.) listed in package.json.
    
    npm run dev
    # This starts a local development server.
    # Open your browser and visit http://localhost:5173 to see your first React app live!

    Option B: Using Create React App (slower)

    npx create-react-app my-react-app
    # This downloads and sets up a full React project named "my-react-app" using Create React App.
    # It may take a few minutes the first time.
    
    cd my-react-app
    # Move into the project directory.
    
    npm start
    # Starts the development server.
    # Open your browser and visit http://localhost:3000 to see your app running!

    Pro Tip for Beginners

    • If npm start or npm run dev doesn’t open your browser, just go to the link shown in your terminal (usually http://localhost:3000 or http://localhost:5173).
    • You can stop the server anytime by pressing Ctrl + C in your terminal.

    Step 3: Open in VS Code

    1. Open VS Code
    2. Click File > Open Folder and select my-react-app
    3. Your React app is ready!

    Step 4: Explore the Files

    • src/App.jsx → Where you’ll start writing your components
    • public/index.html → The single page your React app lives in
    • package.json → List of tools your app uses

    What You Just Did

    • Installed Node & React
    • Created a working React project
    • Ran your first React app in the browser (localhost:5173 or localhost:3000)
    • Took your first real step into the world of frontend dev

    Try This

    Open src/App.jsx and change:

    <h1>Hello React</h1>

    to

    <h1>Hello! I built my first React app!</h1>

    Save → Browser updates instantly

    This is the magic of React!


    What’s Next?

    In the next post:
    “Understanding Components – Your Building Blocks”
    We’ll start writing your first custom React components and begin building your UI piece-by-piece.

  • WP Speed Tip #8 – Remove WooCommerce Dashboard Bloat for Faster Admin

    Why It Matters

    WooCommerce is great — but the default install clutters your admin dashboard with:

    • Analytics blocks you never use
    • Marketing suggestions
    • Extensions you didn’t ask for
    • Admin scripts and styles loading even when not needed

    All this:

    • Slows down your dashboard
    • Eats up memory on shared hosting
    • Distracts you from real work

    The Fix: Disable WooCommerce Admin Junk

    add_filter( 'woocommerce_admin_disabled', '__return_true' );

    This disables the entire new admin interface (called wc-admin) including:

    • WooCommerce Analytics
    • Marketing hub
    • Inbox notices
    • Background data sync

    Your dashboard becomes lighter, faster, and less annoying.


    Optional Cleanup – Remove WooCommerce Styles/Blocks

    remove_action( 'admin_enqueue_scripts', 'wc_enqueue_admin_styles' );
    remove_action( 'admin_enqueue_scripts', 'wc_admin_assets' );

    Only do this if you don’t need advanced reports or order analytics in the dashboard.


    Extra Bloat to Consider Removing:

    • Jetpack integration (not required for Woo)
    • WooCommerce’s built-in “Marketing” menu
    • WooCommerce Blocks plugin (if not using Gutenberg for products)

    Use the “Disable WooCommerce Bloat” plugin if you prefer a click-to-disable interface.


    Real Impact

    After disabling wc-admin, dashboard load time dropped from 3.4s to under 1.2s on a modest shared host.

    Less RAM use, fewer background AJAX calls, and a cleaner experience.


    What You Still Keep After Disabling

    • Orders
    • Products
    • Coupons
    • Standard Woo settings
    • Email functionality
    • Checkout and cart – untouched

    Bonus: Enable Admin Only When Needed

    if ( current_user_can('administrator') ) {
        add_filter( 'woocommerce_admin_disabled', '__return_true' );
    }

    So it only disables for admins, or only on staging/dev.


    Bookmark this tip. Coming up next:
    “Tip #9 – Clean Up Unused Plugins and Themes (Don’t Just Deactivate Them)”

  • WP Speed Tip #7 – Self-Host Fonts to Eliminate External Requests

    Why It Matters

    Most WordPress sites use Google Fonts by default. Here’s what happens:

    • Each font = 2–3 extra external requests
    • These block rendering until fonts are downloaded
    • You lose Core Web Vitals points (especially on mobile)

    Google Fonts are “free,” but they make your site wait.
    Self-hosting = no waiting.


    What’s Wrong with Using Google Fonts?

    • Fonts are fetched from fonts.googleapis.com and fonts.gstatic.com
    • Adds DNS lookup, TLS handshake, and 100–200ms+ delay
    • CLS (Cumulative Layout Shift) increases if fallback fonts are shown first

    The Fix: Self-Host Your Fonts

    Option 1: Use System Fonts (Fastest)

    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;

    Native to all major OS. No downloads. Zero delay.

    Option 2: Self-Host Custom Fonts (Like Roboto, Inter, etc.)

    Step-by-Step:

    1. Download the font from Google Fonts
    2. Use Google Webfonts Helper to:
      • Get .woff2 versions (smallest + fastest)
      • Generate @font-face CSS
    3. Upload fonts to:
      /wp-content/themes/your-theme/fonts/
    4. Paste this @font-face CSS into your theme’s style.css:
    @font-face {
      font-family: 'Inter';
      src: url('/wp-content/themes/your-theme/fonts/inter.woff2') format('woff2');
      font-weight: 400;
      font-style: normal;
      font-display: swap;
    }

    font-display: swap; is critical — it shows fallback font immediately, avoiding CLS.

    1. Replace all theme font references with your custom font.

    What Not to Do

    • Don’t leave both Google Fonts and self-hosted fonts active
    • Don’t upload .ttf or .otf — use .woff2 only

    Real Impact

    Before self-hosting:

    • 6 external font requests
    • CLS penalty
    • Render delay on mobile

    After:

    • 0 external requests
    • Instant font rendering
    • Fewer layout shifts

    Pro Tip:

    If using GeneratePress, Blocksy, or Kadence, they all allow custom font uploads via their settings panel — no code needed.


    Bookmark this tip. Next up:
    “Tip #8 – Remove WooCommerce Dashboard Bloat for Faster Admin”

  • Build Your Own WordPress Theme from Scratch — Here’s the Full Roadmap

    Hey there, fellow builder.
    If you’re tired of bloated themes, messy page builders, and slow-loading sites — you’re not alone.
    I was in the same boat. That’s why I started learning how to build a WordPress theme from scratch.

    And now, I’m documenting the entire process for people like you — bloggers, devs, solopreneurs — who want speed, control, and freedom.

    So here’s the full roadmap. Bookmark this post — every point below will become its own blog post with live code, screenshots, and examples.


    Phase 1: Set Up Your Playground (Pick One)

    1. Local vs Server — Which One’s for You?

    We’ll compare LocalWP (super simple) vs a Server setup (more control).

    TL;DR – Use LocalWP if you want to get started quickly. Use Server if you’re building for real-world deployment.

    2. Installing WordPress (LocalWP & Server Guide)

    Whichever you choose, this post will walk you through installing WordPress from scratch.


    Phase 2: Build the Theme (The Fun Begins)

    3. Your First Barebones Theme

    Create your own folder. Add two files: style.css and index.php. Boom — it’s a theme.

    4. Add a Header & Footer (Don’t Repeat Yourself)

    Split your layout cleanly. Bring in get_header() and get_footer().

    5. Understand the Template Hierarchy (Secret Weapon)

    Why is WordPress showing the wrong layout? This will help you fix that forever.

    6. Master the Loop (Your Theme’s Heartbeat)

    The magic behind showing posts. Learn have_posts(), the_post(), the_title() and friends.

    7. Enqueue CSS & JS (The Right Way)

    Don’t dump <link> or <script> into your header.php. WordPress has a better way.

    8. Build a Blog Index with Excerpts

    Show post title, excerpt, and a “Read More” link. Just like a clean blog homepage should.

    9. Design the Single Post Layout

    Add featured images, meta (author/date), and next/prev post links.

    10. Style It Without Bloat

    Keep it lightweight. No frameworks. Use just CSS and learn to make things beautiful.

    11. Add Menus and Sidebars

    Turn your site into a real website with navigation and optional widgets.

    12. Make It Responsive (Mobile-First)

    Mobile visitors matter. We’ll cover responsive layout, images, and hamburger menus.

    13. Custom Page Templates

    Want a custom layout for your homepage or landing page? This is how you do it.

    14. Prep Your Theme for Use or Sale

    Add screenshot.png, cleanup your code, license it, and zip it for real-world use.


    Phase 3: Go Beyond Basics

    15. Add a Custom Homepage with Featured Posts

    Build a beautiful, minimal homepage without page builders.

    16. Deploy Your Theme to a Live Server

    Move your theme from local to production. No mess, no errors.


    What’s Next?

    In future posts, we may cover:

    • SEO basics for theme devs
    • Gutenberg compatibility
    • Adding WooCommerce support
    • Theme customizer (for end users)
    • Releasing to the WordPress repo or ThemeForest

    Let’s Keep It Interactive

    If you’re following this series:

    1. Drop a comment or message me what setup you’re using (LocalWP or Server).
    2. I’ll tailor the next tutorial based on the most common choices.
    3. Share what kind of site you’re trying to build — blog, store, portfolio?

    Let’s do this. No builders. No bloat. Just pure control.

    🔔 Next post: Local vs Server – Which is Right for You? (Publishing soon)


  • Why Serious Entrepreneurs Should Avoid Bluehost (And What to Use Instead)

    If you’re building a real business — whether it’s a blog, a store, or a startup — your hosting provider is not just a technical detail. It’s your foundation. And Bluehost? It’s a trap disguised as a budget-friendly solution. Here’s why it’s not fit for serious creators.


    1. Cheap Hosting Comes at a Cost

    Yes, Bluehost is cheap. That’s the bait. But once you’re in, the real cost shows up as:

    • Sluggish website speeds
    • Frequent downtime
    • Weak security
    • Support that disappears when you need it most

    These aren’t minor glitches. They directly impact your conversions, SEO rankings, and user trust.


    2. Not Built for Growth

    You might think: I’ll start cheap and upgrade later.
    Here’s the problem — Bluehost isn’t designed to grow with you. Whether you’re running a content-heavy blog, an e-commerce site, or even a basic portfolio, you’ll quickly hit performance ceilings.

    You’ll waste hours debugging server issues instead of building your business.


    3. Bloated Backend and Aggressive Upsells

    Bluehost’s dashboard is packed with clutter. Need a simple task done? Expect delays.
    And every time you log in, you’ll be pitched add-ons and upgrades you don’t need. It’s built to extract money — not to help you scale.


    4. Migration Pain Is Real

    Once you realize Bluehost is holding you back, getting out isn’t easy — especially if you’re not a developer. Their infrastructure isn’t migration-friendly, and their support won’t lift a finger.


    5. Better Alternatives Exist

    You don’t need enterprise-grade hosting, but you do need reliability.
    Here are some solid options that respect your business:

    • Cloudways – Great balance of control and performance
    • SiteGround – Strong support and performance
    • FastComet – Reliable with global datacenters
    • VPS (e.g., DigitalOcean, Linode) – For developers or those ready to scale with custom setups

    Build on a Strong Foundation

    Bluehost is fine for hobby sites and student projects — not for brands aiming to scale. If you’re serious about building a business, skip Bluehost. Save yourself the frustration and start with hosting that’s built to grow with you.