Apt Logic Solutions
Full Spectrum Cloud Services Discuss Your Solution
Case Study

B & G Foods

Rebuilding a 31-page, API-driven corporate site and 45 brand websites on WordPress, as a contract developer with Digital Yalo.

The challenge

I was contracted by Digital Yalo and worked alongside a team of about five other full-time developers on Yalo’s contract with B & G Foods. Our job was to recreate 45 child brand sites in WordPress. The biggest challenges were:

  • Source control implementation and management
  • Custom WordPress template creation
  • Extracting brand-specific data to fill WordPress templates for 45 brands
  • Recreating the corporate site’s advanced, JavaScript-driven components

My contribution

I rebuilt some of the 45 brand sites myself, but focused mostly on the highly specialized, configurable features of the corporate site:

  • JavaScript-driven components for the Brands and Investor Relations pages
  • Brands pages built on the WP REST API, with custom URL tags, permastructs and rewrites
  • A Node.js web scraper to move data over from the old sites
  • Server setups and a GitHub auto-deploy pipeline
  • Multilingual site setups (WPML)

I also wrote documentation and gave hands-on training to other developers on:

  • Local WordPress setup
  • WP Engine SSH key setup
  • Fail conditions and error logging for API request data

Highlighted work

I reverse-engineered the client’s keystone corporate site, with 31 unique API- and JavaScript-driven pages, custom URL structures and complex post-type relationships.

I also took the lead development role on child brand sites such as Weber Seasonings (U.S. and multilingual Canada) and Skinny Girl, and helped build Green Giant Canada, Mrs. Dash and Baking at Home. Here’s a selection:

Highlighted code

Node.js web scraper

To take pressure off our senior back-end developer, I built a web scraping tool that pulled product and recipe details from more than 1,000 pages on the legacy B & G Foods sites.

View source on GitHub
const page = await browser.newPage();

try {
    await page.goto(`${item.link}`, { waitUntil: 'networkidle2' });
    console.log(`SCRAPING PAGE:\n  ${item.title ? item.title : item.link}`);
} catch (e) {
    // keep the error for the final report
    scrapeReportData.pageLoadError.push(item['link']);
    console.log(e.message);
}

const html = await page.content();

// compile domains
let currDomain = dataMod.getDomain(item.link);
if (scrapeReportData.uniqueDomainOrigins.indexOf(currDomain) < 0) {
    scrapeReportData.uniqueDomainOrigins.push(currDomain);
}

// here's where the magic happens: scrape each site using its targets
for (const property of Object.getOwnPropertyNames(SiteSpecificTargets.prototype)) {
    if (typeof siteTargets[property] === 'function' && property != "constructor") {
        if (debug) { console.log('\n\nSiteSpecificTargets: should capture field: ', property) }
        siteTargets[property](debug, html, item, scrapeReportData);
    }
}

await page.close();

Have a project like this?

Tell me about it. Whether it’s a single feature or the whole build, I’ll help you find the most practical way forward.

Contact

Get in touch

Tell me where you're headed and what's in the way, and together we'll map out the most practical path forward.

Desired Services