Smart Adserver

npm package

Description

This package will ease the integration of a Frontity site with Smart Adserver, letting you to configuring it in frontity.settings.js, adding there your client info and the fills for your ads in order to make them appear in a specific slot (see Slot and Fill).

General settings would be in the state, under the smartAdserver namespace.

Specific ad settings would be in the definition of each fill.

Also, you would be able to get the Ad component from libraries and render it in any place.

UserStories

As a Frontity user
I want to install and configure the Smart Adserver package
so that I can show ads from Smart Adserver in a site, without coding.

As a Frontity user
I want to create fills (see Slot and Fill) with ad components
so that I can show ads in specific slots of a Frontity theme.

As a Frontity user
I want to use the Ad component directly
so that I can show ads in my own theme without using fills.

The next two user stores are going to be developed in a different FD:

As a Frontity user
I want to show ads in SSR
so that a visitor would see ads without having to wait ot React to do the CSR.

As a Frontity user
I want to lazyload ads
so that I can show ads without harming visitors experience.

Possible solution

Initial proposed solution (click to open)

Any ad package should expose the Ad component in libraries, under its specific namespace (in this case smartAdserver), or following the rules decided on Slot and Fill.

Apart from that, the implementation of each package would be limited to the specific Ad component and the integration with the ad provider.

Ads should work on SSR, so the way to render them should be using the Script component or something like that. The same for setup tags.

Examples

  • Add an ad above the first post of any archive.
  • Add ads in the middle of a post’s content, below the header, above the footer, etc.

References

  1. Guide explaining how to use the Smart AdServer API to create ad tags: Smart Community and Client Portal
  2. Related Smart AdServer documentation: Smart Community and Client Portal
  3. The implemetation of this component in our old framework (link)
  4. Functions used in the previous component, rendered as strings inside <script> tags (link)

Possible implementation

Possible implementation

:warning: In this proposal wasn’t included how to implement the lazy-load feature.

Installation

The package would be named @frontity/smart-adserver. This would be the way to install it:

npm i @frontity/smart-adserver

This how to configure it in frontity.settings.js:

module.exports = {
  packages: [
    "@frontity/mars-theme",
    "@frontity/tiny-router",
    "@frontity/wp-source",
    {
      name: "@frontity/smart-adserver",
      state: {
        // Global settings.
        smartAdserver: {
          networkId: 620,
          subdomain: "www8"
        }
        // Fills with ads.
        fills: {
          smartads1: {
            slot: "Below Header",
            library: "SmartAd",
            props: {,
              siteId: 103409
              pageId: 659846,
              formatId: 14968,
              tagId: "below-header-14968",
              width: 300,
              height: 600
            }
          },
          smartads2: {
            slot: "Below Content",
            library: "SmartAd",
            props: {
              siteId: 103409
              pageId: 659846,
              formatId: 14968,
              tagId: "below-content-14968",
              width: 300,
              height: 600
            }
          }
        }
      }
    }
  ]
}

Settings

All the following settings are required:

state.smartAdserver.networkId

The network id assigned to this client (e.g. 620). Used in the Root component to setup the Smart Adserver library. Required.

state.smartAdserver.subdomain

The subdomain assigned to this client (e.g. www8). Used in the Root component to setup the Smart Adserver library.

Roots

roots.smartAdserver

The component that loads and sets up the Smart Adserver libraries. It should use networkId and subdomain from state.smartAdserver.

import React from "react";
import { Head, connect } from "frontity";

const SmartAdserver = ({ state }) => {
  const { networkId, subdomain } = state.smartAdserver;

  React.useEffect(() => {
    window.sas = window.sas || { cmd: [] };
    window.sas.cmd.push(function () {
      window.sas.setup({
        networkid: networkId,
        domain: `//${subdomain}.smartadserver.com`,
        async: true,
      });
    });
  }, []);

  return (
    <Head>
      <script
        src={`//ced.sascdn.com/tag/${networkId}/smart.js`}
        async
      />
    </Head>
  );
};

export default connect(SmartAdserver);

Libraries

libraries.fills.SmartAd

Component that renders an ad. It would receive the following properties, defined for each ad in state.fills:

  • siteId (number): identifies the website on Smart Adserver.
  • pageId (number): identifies the page on a website.
  • formatId (number): identifies the format (medium rectangle, skyscraper, etc.).
  • tagId (string): id for the <div> container, just to know where to render it.

    :bulb: This prop was generated like this (in the old implementation).

  • target (string, optional): used to pass keywords and key=value pairs.

    :bulb: This value was got from entities in the past (see this).

  • width (number): value in pixels.
  • height (number): value in pixels.

This could be the implementation:

import React from "react";
import { css } from "frontity";

const SmartAd = ({
  siteId,
  pageId,
  formatId,
  tagId,
  target,
  width,
  height,
}) => {
  React.useEffect(() => {
    const sas = window.sas || (window.sas = { cmd: [] });
    sas.cmd.push(function () {
      sas.call("std", {
        siteId,
        pageId,
        formatId,
        target,
        width,
        height,
        tagId,
      });
    });
  }, []);

  return (
    <div
      id={tagId}
      css={css`
        width: ${width}px;
        height: ${height}px;
      `}
    />
  );
};

export default SmartAd;

Issues

  • Settings and Root component
  • SmartAd component

Awesome work @david.

For what I see in their site and twitter, it seems like they consider smart ad and server as three different words:

If that’s the case, I would use the namespace SmartAdServer and the package name @frontity/smart-ad-server instead. What do you think?

Also, could you please add links to their documentation?

I chose the same name they use in their homepage, but I have no problem changing it.

  • Smart Adserver | The Most Powerful Adserving and RTB Platform

I’ll add the links in the References section.

You’re right. It looks like they use both indistinctly so keep it as it is. Sorry about that.

I’ve edited the implementation proposal to replace the <Script> component by React.useEffect hooks.

@David I’d like your help on a couple of things! :slight_smile:

  1. I’d like to propose that we show a warning in development if the user does not pass the networkId and the subdomain props using the frontity @frontity/error package. As far as I can tell those 2 props are always necessary and the ads will not work if they are not set.

    Is there some use case for the ads where those props might be added to the state later (not in the frontity.settings.js file) that would make such a warning confusing? I’m thinking of some kind of lazy initialization of the ads, where the networkId and the subdomain are added to the state.smartAdserver later, but I’m not sure if this is ever going to come up.

  2. I notice that the “standard tagging” via sas.call("std", {...options}) is not recommended according to the SmartAds tagging guide:

    Is there a reason why we would need the “std” call specifically or should we allow all types of calls? I see that in the previous version of the framework, the SmartAd component accepted a callType prop which could be "std" | "iframe". I’m guessing that the iframe has been deprecated since then and the “onecall” is a new type?

  3. What was your reason for replacing the tag in the head with the call inside of useEffect? In my tests right now, if I use the useEffect, the request fails to complete because of google chrome’s restriction:

    however, when I just put the script in the head like:

    <script type="application/javascript">
         {`
         window.sas = window.sas || { cmd: [] };
         window.sas.cmd.push(function () {
           window.sas.setup({
             networkid: ${networkId},
             domain: "//${subdomain}.smartadserver.com",
             async: true,
           });
         });
       `}
    </script>
    

    I have no such problem. It seems that this snippet should not be run inside useEffect because it’s doing a document.write(), however it seems that it’s fine to do it inside the head because the DOM is not parsed yet at that time.

That’s a good point. To avoid limiting people to define those settings in their frontity.settings.js file I would add the warning to the React component, not the init action :slightly_smiling_face:

One of the design principles I’m working on is precisely to avoid initialization as much as possible.

Right. If I remember correctly, the thing was that OneCall tagging didn’t work with SPA because you could only make a single call for all the ads defined. You couldn’t choose where to render those ads either.

But, I just read again the SmartAdserver documentation and I saw there is now something called OneCall with POST method that allows you to make multiple calls, and it is also possible to define a specific tag for each ad unit so you can render each ad unit in a specific element in the DOM.

For example, you could use OneCall each time the link changes – with the particular ads that are going to be rendered in that page and their tagId –, and then call sas.render(tagId) inside the SmartAd component, though I don’t know if something like that would be feasible, to be honest.

The std call just makes a single call for each ad unit so it was simpler to implement.

Uhm, I don’t really understand what’s happening there. I mean, the JavaScript snippet is just adding a function that will be called once the SmartAdserver library is loaded. It should not be using document.write() before the DOM is parsed, right?

Final Implementation

This has been implemented in https://github.com/frontity/frontity/pull/569! :fire:

The package can be installed like:

npm i @frontity/smart-adserver

The user can configure the package in their frontity.settings.js file. This way, the ads will be rendered in the user’s theme using the Slot and Fill pattern

module.exports = {
  packages: [
    "@frontity/mars-theme",
    "@frontity/tiny-router",
    "@frontity/wp-source",
    {
      name: "@frontity/smart-adserver",
      state: {
        // Global settings.
        smartAdserver: {
          networkId: 620,
          subdomain: "www8",
        },
        fills: {
          smartAdserver: {
            // This ad is using the 'std' call: https://support.smartadserver.com/s/article/Tagging-guide
            stdAd: {
              slot: "header",
              library: "smartAdserver.SmartAd",
              props: {
                callType: "std",
                siteId: 103409,
                pageId: 659846,
                formatId: 14968,
                tagId: "below-header-14968", // The id of the container where we render the ad.
                minHeight: 100, // In px, optional.
              },
            },
            // This ad is using the 'iframe' call: https://support.smartadserver.com/s/article/Tagging-guide
            iframeAd: {
              slot: "content",
              library: "smartAdserver.SmartAd",
              props: {
                siteId: 103409,
                pageId: 659846,
                formatId: 14968,
                tagId: "below-content-14968",
                width: 300, // Should be specified if callType === 'iframe'.
                height: 600, // Should also be specified if callType === 'iframe'.
              },
            },
          },
        },
      },
    },
  ],
};

The Root exposed by the SmartAdsever package:

const Root: React.FC<Connect<SmartAdserver>> = ({ state }) => {
  const { networkId, subdomain } = state.smartAdserver;

  useEffect(() => {
    window.sas = window.sas || { cmd: [] };

    // Set the flag that means that the Smart Adserver library has been
    // initialized. This is flag is checked by the individual Ad components
    // before making ad calls.
    state.smartAdserver.isLoaded = true;

    if (!networkId) {
      warn(
        "state.smartAdserver.networkId was not defined. The Smart Adserver library will not be able to load."
      );
    }

    if (!subdomain) {
      warn(
        "state.smartAdserver.subdomain was not defined. The Smart Adserver library will not be able to load."
      );
    }

    // Set up the Smart Adserver library
    window.sas.cmd.push(function () {
      window.sas.setup({
        networkid: networkId,
        domain: `//${subdomain}.smartadserver.com`,
        async: true,
      });
    });

    // Clean up when component unmounts.
    return () => {
      state.smartAdserver.isLoaded = false;
    };
  }, [networkId, subdomain, state.smartAdserver]);

  return (
    <Head>
      <script src={`//ced.sascdn.com/tag/${networkId}/smart.js`} async />
    </Head>
  );
};

The SmartAd component which renders the ad:

const SmartAd: React.FC<SmartAdProps> = ({
  callType,
  siteId,
  pageId,
  formatId,
  tagId,
  target,
  width,
  height,
  minHeight,
  css: styling,
  ...props
}) => {
  const { state } = useConnect<SmartAdserver>();
  const { isLoaded } = state.smartAdserver;

  // If the tagId was not passed as a prop, we fall back to the default used by
  // Smart Adserver.
  if (!tagId) {
    tagId = `sas_${formatId}`;
  }

  useEffect(() => {
    // If true, it means that `setup()` of the SmartAdserver has been called.
    if (isLoaded) {
      window.sas.cmd.push(function () {
        window.sas.call(callType, {
          siteId,
          pageId,
          formatId,
          target,
          width,
          height,
          tagId,
        });
      });
    }
  }, [
    isLoaded,
    callType,
    siteId,
    pageId,
    formatId,
    tagId,
    target,
    width,
    height,
  ]);

  return (
    <div
      id={tagId}
      css={[styles({ callType, width, height, minHeight }), styling]}
      {...props}
    />
  );
};

export default connect(SmartAd, { injectProps: false });

In addition to the props specified above like callType, target, etc. the user also has an opportunity to pass any other props to the <SmartAd /> component, e.g. to add custom styling via the css prop:

<SmartAd
  callType="std"
  siteId={siteId}
  pageId={pageId}
  formatId={formatId}
  tagId="test-smartad"
  css={css`
    border: 5px solid dotted;
  `}
/>

Hey @mmczaplinski :wave:t3:

Thanks for sharing. I’m interested to implement it. :nerd_face:

You remember the issue with the adblock because adsbygoogle object was not present, described here in your comment :point_right: WordPress comments package?

So, i just wanted to ask if you’ve been able to test this with Smart Adserver?

Please let me know.
Thank you very much.

Hey @dejangeorgiev !

I’ve tested the SmartAds package with the adblocker enabled (I use uBlock Origin) and I’ve had no issues.

Of course, the ads did not load because the extension blocks all the network requests to the smartad server but it’s not something that we could do very much about in Frontity itself :slight_smile: .