UserAgent and isMobile detection

Hello!

I need to detect if the request is a mobile request or not. Does Frontity have something like isMobile function? Or how can I access to the userAgent request?

Thanks!!

Yes, you will be able to access any header, including ‘user-agent’, as soon as we add server extensibility. It’s one of the next steps as we need it to make AMP work as well so it shouldn’t take long.

We use Koa in the server because it’s way smaller than Express and works great with async/await. You can use this Koa functions to extend Frontity’s server at your will.

It’s going to be very similar to this:

// packages/your-package/src/index.js

// Default export, with React app.
export default {
  roots: { ... },
  state: { ... },
  actions: { ... },
  libraries: { ... }
};

// server export, to extend the server.
export const server = app => {
  app.use(ctx => {
    ctx.headers['user-agent'];
  });
};

If you want to include server-only code or a library that is big and don’t want to include in the client bundle, you’d need to delete index.js and create a client.js and a server.js file.

For example, you may want to use this: https://github.com/rvboris/koa-useragent (~15kbs):

// packages/your-package/src/client.js
export default {
  state: { ... },
  actions: { ... },
  libraries: { ... }
};
// packages/your-package/src/server.js
import userAgent from 'koa-useragent';  // import it only in the server

export default {
  state: { ... },
  actions: { ... },
  libraries: { ... }
};

export const server = app => {
  app.use(userAgent);
  app.use(ctx => {
    ctx.userAgent; // do something with this
  });
};

You can use the server function to modify the state of your site. The current settings are exposed in ctx.settings:

export const server = app => {
  app.use(ctx => {
    // Change the state of the settings.
    ctx.settings.state.frontity.isMobile = ctx.userAgent.isMobile;
  });
};

Finally, you can consume that state in React:

const MyComponent = ({ state }) =>  {
  if (state.frontity.isMobile) return <MobileThing />;
  return <NotMobileThing />;
}

Are you going to use the user-agent to cache different HTML for mobile/tablet/desktop?

That looks great! Until that is done on production I made a hack on @frontity/core/server/index.tsx file:

I added the

import userAgent from "koa-useragent";

After the line:

app.use(get("/favicon.ico", serve("./")));

I added:

app.use(userAgent);

And after the line:

const settings = await getSettings({ url: ctx.href, name: ctx.query.name });

Added:

settings.state.frontity.isMobile = ctx.userAgent.isMobile;

After that I ran the following commands:

sudo rm -R node_modules
npm install koa-useragent@2.0.0
npm install @types/koa-useragent
npx frontity dev

Now I can use the state.frontity.isMobile :clap:

Are you going to use the user-agent to cache different HTML for mobile/tablet/desktop?

Yes, I think I can not create the exactly the same code for mobile and desktop as there is some widgets that only needed to be loaded on mobile or desktop. If the website is behind on a CDN it must have the capability of make user-agent detection, to check if there is a mobile/tablet/desktop request. I think nowadays that is not a problem.

Thank you! :blush:

I’m glad it works :relaxed:


Yes, but you’d have to make sure you are using the exact same regular expression than your CDN is using or you may get into trouble with edge cases.

They usually publish the regular expressions in the docs. For example, these are the regular expressions used by KeyCDN (Nginx syntax):

map $http_user_agent $ua_device {
    default "desktop";
    "~*(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge\ |maemo|midp|mmp|mobile.+firefox|netfront|opera\ m(ob|in)i|palm(\ os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows\ ce|xda|xiino/i" "mobile";
    "~*android|ipad|playbook|silk/i" "tablet";
}

@javier, you know more SEO than we do but I’d like to state a couple of things here for other people that may read this in the future:

  • The relevant content included on the three versions (desktop, tablet, mobile) should be the same or Google may penalize your site.
  • To avoid the burden of having to configure the CDN to do “user-agent caching”, you can always do the things the old, responsive way, using media queries. Thanks to React, that could be as simple as:
// Hidden on desktop.
const Mobile = styled.div`
  display: none;
  @media (max-width: 768px) {
    display: block;
  }
`;

// Hidden on mobile.
const Desktop = styled.div`
  display: block;
  @media (max-width: 768px) {
    display: none;
  }
`;

const MyComponent = () => (
  <>
    <Mobile>
      Content that only shows in mobile.
    </Mobile>
    <Desktop>
      Content that only shows in desktop.
    </Desktop>
  </>
);

Of course, there are more ways to achieve that in React :slight_smile:

Thanks for the replay! Initially I will try to avoid to have two versions. But I needed to know if it is possible do it on some way. :smiley:

1 Like

In fact, I have problems on the next scenario: Inside a post I have the main content with paragraphs and a sidebar with ads (ad1, ad2 and ad3) and related posts divs. I have no problem on show or hide the divs with @media query. But I can’t do that with ads.

I am trying to use CSS grid (each

is a row) but is complicated calculate the height that I need for the sidebar content.

On mobile version I need to position a ad after the fourth

(for example). I can’t see how can I do this only using CSS.

If you have any idea how to solve this jus tell me! :smiley:

How are you positioning the ads after the fourth paragraph?

We used to do it with Html2React but I wonder what’s your solution.

That is the problem, I don’t know how to make it work using only CSS. On our actual website is generated by PHP and we have two versions (mobile and desktop). The only solution I can imagine is create ads divs duplicated and before load the smartadserver script execute a javascript logic to determine which div is visible and set it a ID attribute (wich it will be used by smartadserver script).

Have you thought about using window.matchMedia()?

Maybe something like this:

const getType = () => {
  const mq = window.matchMedia( "(max-width: 570px)" );
  if (mq.matches) {
    // window width is at less than 570px
    return "mobile";
  } else {
    // window width is greater than 570px
    return "desktop";
  }
}

const SmartAd = ({ someAdParams, type }) => {
  useEffect(() => {
     // Only initialize this Ad if type matches.
     if (type === getType)
       initializeAd(someAdParams);
  }, []);

  return (
    <div
      id={someAdParams.tagId}
      width={someAdParams.width}
      height={someAdParams.height}
    />
  );
};

Then use type="mobile" or type="desktop" in your components:

<!-- in the places where it should show an ad in mobile -->
<SmartAd someAdParams={someAdParams} type="mobile" />

<!-- in the places where it should show an ad in desktop -->
<SmartAd someAdParams={someAdParams} type="desktop" />

I wonder if you need to use the same tagIds and that could cause problems with the initiliazation?

Hey @luisherranz, how is this going? Is it done already?
Whats the simpliest way to know if it’s mobile at the moment?

You can use beforeSSR to get the Koa ctx and inspect the User-Agent header.

const beforeSSR = ({ state, actions }) => ({ ctx }) => {
  const ua = ctx.get("user-agent");
  // ...
}

but if you do that you cannot cache the response, so I don’t recommend you to do so unless your cache layer also supports it, like this: User Device-Based Caching - KeyCDN Support.

User Agent detection is not a recommended technique for modern web apps. You can use JavaScript window.matchMedia() method to detect a mobile device based on the CSS media query.

if (window.matchMedia("(max-width: 767px)").matches)
{
// The viewport is less than 768 pixels wide
document.write("This is a mobile device.");
}

Another approach would be a responsive media query. You could presume that a mobile phone has a screen size greater than x and less than y.

For example:

@media only screen and (min-width: 320px) and (max-width: 600px) {}

You may also use navigator.userAgentData.mobile .

const isMobile = navigator.userAgentData.mobile;