Categories
discuss

JSON-LD schema with GatsbyJS for rich snippets

I have a basic blog setup with Gatsby and at the time of posting this question there lacks good documentation for SEO components. There are examples of basic SEO components but what I am wanting is a little more in-depth. Maybe, if a solution is reached here it could be contributed to the Gatsby docs for others to benefit.

On top of the usual title and description meta tags and the facebook/twitter open graph meta (which I have done already), I want to add structured data for rich snippets which will vary depending on what the blog post type is. For example, I might have a regular post which would print Article schema, some posts might be How-to, in which case I’d like to print HowTo schema instead of Article. At some point I might write a post with would suit FAQ schema.

I don’t know if this is the best approach but here’s what I’m thinking:

1. In frontmatter set the schema type I want to true, leave the rest false.

I am also thinking of storing the schema data in the frontmatter but as this data is quite complex and will vary from post type to post type (Article, HowTo etc.) I’m not sure if this is yet a good idea?

---
title: Hello World
description: How to say hello
article: false
how-to: true
faq: false
---

2. Test for true/false in the SEO component and print the correct schema.

Below is my entire SEO component, which obviously doesn’t work but you can hopefully see where my thinking is headed. I have dissected and borrowed from the gatsby advanced starter component and the gatsby starter prismic component but neither do quite what I need. Here’s mine:

import React from "react"
import Helmet from "react-helmet"
import PropTypes from "prop-types"
import { useStaticQuery, graphql } from "gatsby"
import Facebook from "./Facebook"
import Twitter from "./Twitter"

const SEO = ({
  title,
  desc,
  banner,
  pathname,
  published,
  modified,
  article,
  webpage,
  node,
}) => {
  const { site } = useStaticQuery(query)

  const {
    buildTime,
    siteMetadata: {
      siteUrl,
      defaultTitle,
      defaultDescription,
      defaultBanner,
      headline,
      siteLanguage,
      ogLanguage,
      author,
      twitter,
      facebook,
    },
  } = site

  const seo = {
    title: title || defaultTitle,
    description: desc || defaultDescription,
    image: `${siteUrl}${banner || defaultBanner}`,
    url: `${siteUrl}${pathname || "/"}`,
    date_published: published,
    date_modified: modified,
  }

  // Default Website Schema
  const schemaOrgJSONLD = [
    {
      "@context": "http://schema.org",
      "@type": "WebSite",
      url: siteUrl,
      name: defaultTitle,
      alternateName: headline ? headline : "",
    },
  ]

  if (howto) {
    schemaOrgJSONLD.push({
      /* HowTo Schema here */
    })
  }
  
  if (faq) {
    schemaOrgJSONLD.push({
      /* FAQ Schema here */
    })
  }

  if (article) {
    schemaOrgJSONLD.push({
      /* Regular Article Schema */
      "@context": "http://schema.org",
      "@type": "Article",
      author: {
        "@type": "Person",
        name: author,
      },
      copyrightHolder: {
        "@type": "Person",
        name: author,
      },
      copyrightYear: "2019",
      creator: {
        "@type": "Person",
        name: author,
      },
      publisher: {
        "@type": "Organization",
        name: author,
        logo: {
          "@type": "ImageObject",
          url: `${siteUrl}${defaultBanner}`,
        },
      },
      datePublished: seo.date_published,
      dateModified: seo.date_modified,
      description: seo.description,
      headline: seo.title,
      inLanguage: siteLanguage,
      url: seo.url,
      name: seo.title,
      image: {
        "@type": "ImageObject",
        url: seo.image,
      },
      mainEntityOfPage: seo.url,
    })
  }

  return (
    <>
      <Helmet title={seo.title}>
        <html lang={siteLanguage} />
        <meta name="description" content={seo.description} />
        <meta name="image" content={seo.image} />
        {/* Schema.org tags */}
        <script type="application/ld+json">
          {JSON.stringify(schemaOrgJSONLD)}
        </script>
      </Helmet>
      <Facebook
        desc={seo.description}
        image={seo.image}
        title={seo.title}
        type={article ? "article" : "website"}
        url={seo.url}
        locale={ogLanguage}
        name={facebook}
      />
      <Twitter
        title={seo.title}
        image={seo.image}
        desc={seo.description}
        username={twitter}
      />
    </>
  )
}

export default SEO

SEO.propTypes = {
  title: PropTypes.string,
  desc: PropTypes.string,
  banner: PropTypes.string,
  pathname: PropTypes.string,
  published: PropTypes.string,
  modified: PropTypes.string,
  article: PropTypes.bool,
  webpage: PropTypes.bool,
  node: PropTypes.object,
}

SEO.defaultProps = {
  title: null,
  desc: null,
  banner: null,
  pathname: null,
  published: null,
  modified: null,
  article: false,
  webpage: false,
  node: null,
}

const query = graphql`
  query SEO {
    site {
      buildTime(formatString: "YYYY-MM-DD")
      siteMetadata {
        siteUrl
        defaultTitle: title
        defaultDescription: description
        defaultBanner: logo
        headline
        siteLanguage
        ogLanguage
        author
        logo
        twitter
        facebook
      }
    }
  }
`

The problems I can see are:

  1. How to test for what schema type to use and print it
  2. Include breadcrumbs schema for all types
  3. Print only a single schema JSON-LD script tag, avoiding any duplicate schema
  4. Is using frontmatter in markdown files suitable to store complex schema data
  5. Retrieving frontmatter data for schema

Answer

I settled on this solution.

In frontmatter:

---
type: howto // Use either 'article' or 'howto'
---

Query for it with GraphQL like you would for your other data:

frontmatter {
 title
 published(formatString: "MMMM DD, YYYY")
 modified(formatString: "MMMM DD, YYYY")
 description
 type
}

Pass it to your SEO component:

<SEO
 title={post.frontmatter.title}
 desc={post.frontmatter.description}
 published={post.frontmatter.published}
 modified={post.frontmatter.modified}
 type={post.frontmatter.type}
/>

In your SEO component, you can use it like this (do the same for all your types). You can setup your Posts and SEO component for as my types as you need, FAQ, Course etc:

const schemaType = type

if (schemaType === "howto") {
 schemaHowTo = {
  // Your howto schema here
 }
}

if (schemaType === "article") {
 schemaArticle = {
  // Your article schema here
 }
}

Finally, in React Helmet we have:

<Helmet>
 {schemaType === "howto" && (
  <script type="application/ld+json">
   {JSON.stringify(schemaHowTo)}
  </script>
 )}
 {schemaType === "article" && (
  <script type="application/ld+json">
   {JSON.stringify(schemaArticle)}
  </script>
 )}
...
<Helmet>
Categories
discuss

Get “supportLibVersion” , “playServicesVersion” and “androidMapsUtilsVersion” Value in configuration react-native-maps

I am installing react-native-maps. In the installation guide, on Build configuration on Android in Section 3.1, it shows:

buildscript {
    ext {
        buildToolsVersion = "xxx"
        minSdkVersion = xxx
        compileSdkVersion = xxx
        targetSdkVersion = xxx
        supportLibVersion = "xxx"
        playServicesVersion = "xxx" // or set latest version
        androidMapsUtilsVersion = "xxx"
    }
}
...

How can i get supportLibVersion , playServicesVersion and androidMapsUtilsVersion values?

Answer

In general, all libaries can be found in the maven repository. For some you have to see a concrete library to get the version number (this number is the number you put in the build.grade file). Usually, all libraries that are part of a group, should support the same version numbers (otherwise you get a compile error and you need to check the concrete version and make sure that it exists for the libraries that you need).

googlePlayServicesVersion:
https://mvnrepository.com/artifact/com.google.android.gms/play-services-maps

androidMapsUtilsVersion:
https://mvnrepository.com/artifact/com.google.maps.android/android-maps-utils

supportLibVersion:
https://mvnrepository.com/search?q=com.android.support
See within a library. e.g. https://mvnrepository.com/artifact/com.android.support/appcompat-v7

Categories
discuss

Using Spring Boot 2 OAuth Client and Resourceserver in the same context

I’d like my spring boot application to serve a protected frontend, as well as being an API resource server for said frontend at the same time, but I can’t get the oauth stuff working.

What I want is the spring boot application to return a 302 redirect to the oauth server (gitlab in my case) when the browser requests the index.html without a token, so the user is sent to the login form. But I also want that the API to return a 401 when the API is called without a token, as I think a 302 redirect to a login page is not very useful there.

In pseudo code:

if document_url == /index.html and token not valid
  return 302 https//gitlab/loginpage
if document_url == /api/restcall and token not valid
  return 401
server document_url

I am working with spring boot 2.1, regarding oauth my pom.xml contains

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-oauth2-client</artifactId>
    </dependency>

This is my naive try in the SecurityConfig

public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests().antMatchers("/index.html").authenticated()
        .and()
            .oauth2Login().loginPage("/oauth2/authorization/gitlab")

        .and()
            .authorizeRequests().antMatchers("/api/restcall").authenticated()
        .and()
            .oauth2ResourceServer().jwt();
    }
}

Both configurations (oauth2Login and oauth2ResourceServer) work fine for themself. But as soon as I combine them the last one wins (so in the above example there would be no 302 and the browser would also see a 401 for the index.html). I presume they share some configuration objects so the last write wins.

Is there an (easy) way to get what I want? I know spring can do almost anything, but I would very much not to end up manually configuring a gazillion beans …

Update:

I’ve made a minimal example (including @dur’s suggestion) of my code here

Answer

You need to create multiple configurations and restrict them only to specific URL patterns using requestMatcher. Based on your example, your configurations should look like this:

SecurityConfigHTML

public class SecurityConfigHTML extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .requestMatchers().antMatchers("/index.html")
                .and()
                .authorizeRequests().anyRequest().authenticated()
                .and()
                .oauth2Login().loginPage("/oauth2/authorization/gitlab");
    }
}

SecurityConfigAPI

public class SecurityConfigAPI extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .requestMatchers().antMatchers("/api/call")
                .and()
                .authorizeRequests().anyRequest().authenticated()
                .and()
                .oauth2ResourceServer().jwt();
    }
}
Source: stackoverflow
Text is available under the Creative Commons Attribution-ShareAlike License; additional terms may apply. By using this site, you agree to the Privacy Policy, and Copyright Policy. Content is available under CC BY-SA 3.0 unless otherwise noted. The answers/resolutions are collected from stackoverflow, are licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0 © No Copyrights, All Questions are retrived from public domain..