Problem with jquery(react-owl-carousel) in gatsby/react - reactjs

please help. I was trying to use react-owl-carousel in a gatsby site but I have run into a problem. The error says:
Here's my gatsby-node.js
exports.onCreateWebpackConfig = ({ plugins, actions }) => {
actions.setWebpackConfig({
plugins: [
plugins.define({
$: 'jquery',
jQuery: 'jquery',
'window.jQuery': 'jquery',
}),
],
})
}

try this in your gatsby-node.js
exports.onCreateWebpackConfig = ({ actions }) => {
const { setWebpackConfig } = actions
setWebpackConfig({
externals: {
jquery: 'jQuery', // important: 'Q' capitalized
},
})
}
and in html.js
import React from 'react'
import PropTypes from 'prop-types'
export default function HTML(props) {
return (
<html {...props.htmlAttributes}>
<head>
<script
src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossOrigin="anonymous"
/>
<meta charSet="utf-8" />
<meta httpEquiv="x-ua-compatible" content="ie=edge" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
{props.headComponents}
</head>
<body {...props.bodyAttributes}>
{props.preBodyComponents}
<div
key={`body`}
id="___gatsby"
dangerouslySetInnerHTML={{ __html: props.body }}
/>
{props.postBodyComponents}
</body>
</html>
)
}
HTML.propTypes = {
htmlAttributes: PropTypes.object,
headComponents: PropTypes.array,
bodyAttributes: PropTypes.object,
preBodyComponents: PropTypes.array,
body: PropTypes.string,
postBodyComponents: PropTypes.array,
}
put html.js in the src folder

Related

Next.js | How to improve seo when doing dynamic routing with ssr

I am writing dynamic routes code in, like the code below. So my question is, this dynamic routes page is not reflected in google at all. I have dynamically set the title of this page, but when I search for that title in google, it doesn't show up. What should I do?
src/pages/salon/[[...salon]].js (abridged edition)
import React from 'react'
// Firebase
import { db } from "../../../firebaseConfig"
import { doc, getDoc, updateDoc, arrayUnion, arrayRemove } from "firebase/firestore"
import { getAuth, onAuthStateChanged } from "firebase/auth"
const Salon = ({ id, tag, data }) => {
return (
<>
<NextSeo
title={data.title}
description={data.description}
url={data.url}
canonical={data.url}
openGraph={{
url: data.url,
title: data.title,
description: data.description,
type: "article",
images: [
{
url: data.mainImages[1],
width: 800,
height: 600,
alt: "Image",
}],
}}
/>
<div>{data.salonName}【{data.salonNameKana}】</div>
</>
)
}
export default Salon
export async function getServerSideProps(context) {
const url = context.params.salon
const salonRef = doc(db, "salons", url[0])
const salonSnap = await getDoc(salonRef)
const data = salonSnap.data()
if (!data) {
return {
redirect: {
destination: '/',
permanent: false,
},
}
}
return {
props: {
id: url[0],
tag: url[1] ? url[1] : '',
data: data
}
};
}
src/pages/salon/[[...salon]].js (full edition)
import React, { useEffect } from "react"
import Link from 'next/link'
import { useRouter } from 'next/router'
import Image from "next/image"
// firebase関連
import { db } from "../../firebaseConfig"
import {
collection,
orderBy,
query,
getDocs,
limit,
where
} from 'firebase/firestore'
// fontawesome
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { faSearch, faMapMarkedAlt, faSubway } from "#fortawesome/free-solid-svg-icons"
// Redux関連
import { useSelector } from 'react-redux'
import { NextSeo } from "next-seo"
const Index = ({newSalonList}) => {
const router = useRouter()
const area = useSelector((state) => state.searchKey.area)
return (
<div className="Top">
<NextSeo />
{/* 検索 */}
<div className="search">
<div className="reserveText">イメコンサロンを検索する</div>
<div className="area">
<p>{area}</p>
<Link href="area">
<a><button>エリア変更</button></a>
</Link>
</div>
<div className="form">
<form action="/search" method="GET" className="search-box">
<input type="text" name="query" placeholder="サロン名・エリア・メニューなどから検索" />
<button type="submit" value=" 検索"><FontAwesomeIcon icon={faSearch} /></button>
</form>
</div>
<div className="common_relative_image mainImg">
<Image
src='/imecon-personal-color.jpeg'
alt="mainTopImg"
className="mainImg"
layout="fill"
objectFit="contain"
priority
/>
</div>
<div className="searchWay">
<button className="searchWayItem" onClick={(e) =>{
e.preventDefault()
router.push({
pathname: "/area",
// 検索する地域をクリックした後の遷移先のURLを渡す
query: {to:'area-search'}
})
}}>
<div className="areaImage common_relative_image">
<Image
src='/JapanMap.svg'
alt="japanMap"
layout="fill"
objectFit="contain"
/>
</div>
<p><span>エリア</span><br />から探す</p>
</button>
<Link href="station">
<a className="searchWayItem" style={{cursor: "default", background: "#D9D9D9"}} onClick={ (event) => event.preventDefault() }>
<FontAwesomeIcon icon={faSubway} />
<p><span>駅</span>から探す</p>
<small style={{color: "red"}}>coming soon</small>
</a>
</Link>
<Link href="location">
<a className="searchWayItem" style={{cursor: "default", background: "#D9D9D9"}} onClick={ (event) => event.preventDefault() }>
<FontAwesomeIcon icon={faMapMarkedAlt} />
<p><span>現在地</span><br />から探す</p>
<small style={{color: "red"}}>coming soon</small>
</a>
</Link>
</div>
</div>
{/* ブックマークから探す */}
<Link href="/bookmark">
<a className="bookMark">
<svg xmlns="http://www.w3.org/2000/svg" width="24" fill="#FF0100" height="24" viewBox="0 0 24 24"><path d="M12 4.248c-3.148-5.402-12-3.825-12 2.944 0 4.661 5.571 9.427 12 15.808 6.43-6.381 12-11.147 12-15.808 0-6.792-8.875-8.306-12-2.944z"/></svg>
ブックマークから探す
</a>
</Link>
{/* お知らせ */}
<div className="information">
<div className="common_title">| Information</div>
<ul>
<li>
<Link href="/tosalons">
<a>掲載をご希望のサロン様はこちら</a>
</Link>
</li>
</ul>
</div>
{/* 新着サロン */}
<div className="newSalon">
<div className="common_title">| 新着のサロン</div>
<ul>
{newSalonList.map((data) => {
return (
// 新着順のサロンを表示:useEffectで格納したデータ
<li className="newSalonMain" key={data.salonId}>
<Link href={'/salon/' + data.salonId}>
<a className="newSalonMainList">
<div className="newSalonMainListImage common_relative_image">
<Image
src={data.mainImageUrl}
alt="salonMainImage"
layout="fill"
objectFit="contain"
/>
</div>
<div className="newSalonMainListInfo">
<p className="salonName">{data.salonName}{data.salonNameKana}</p>
<div className="salonTraffic">
<div className="common_relative_image salonTrafficImg">
<Image
src='/traffic.svg'
alt="traffic"
layout="fill"
objectFit="contain"
/>
</div>
<span>{data.address}</span>
</div>
<div className="newSalonMainListInfoMenu">
<div className="newSalonMainListInfoMenuTitle">{data.title}</div>
<div className="newSalonMainListInfoMenuPrice">¥{data.price}</div>
</div>
</div>
</a>
</Link>
</li>
)
})}
</ul>
</div>
</div>
)
}
export default Index
export async function getServerSideProps() {
// firestoreから新着順サロンのドキュメントを5つ取得
const salons = query(collection(db, "salons"), where('publish', "==", true), orderBy('createAt', "desc"), limit(5))
const querySnapshot = await getDocs(salons)
// 一旦useEffect内で配列を作り、格納する。後でこの配列をuseStateに格納
let subSalonList = []
// 取得した新着順サロンのドキュメントから情報を取得し、subSalonListに格納
await querySnapshot.forEach((doc) => {
subSalonList = [...subSalonList, {
salonId: doc.id,
salonName: doc._document.data.value.mapValue.fields.salonName.stringValue,
salonNameKana: doc._document.data.value.mapValue.fields.salonNameKana ? '【' + doc._document.data.value.mapValue.fields.salonNameKana.stringValue + '】': '',
address: doc._document.data.value.mapValue.fields.access ? doc._document.data.value.mapValue.fields.access.stringValue: '',
mainImageUrl: doc._document.data.value.mapValue.fields.mainImages.mapValue.fields[1].stringValue,
}]
})
// サブコレクションの情報は↑で一緒に取得できなかったので、↓で格納する。subSalonListをループして、salonIdを元に順番に情報を取得し、subSalonListに格納している。
for (const value of subSalonList) {
try {
// salonIdを元にmenuサブコレクション下のドキュメントでtopがtrueのものを検索&取得
const q = query(collection(db, "salons", value.salonId, "menu"))
const querySnapshot2 = await getDocs(q)
value.title = querySnapshot2.docs[0]._document.data.value.mapValue.fields.title.stringValue
value.price = querySnapshot2.docs[0]._document.data.value.mapValue.fields.price.integerValue.toLocaleString()
value.time = querySnapshot2.docs[0]._document.data.value.mapValue.fields.time.integerValue.toLocaleString()
} catch(err) {
console.log(err)
return 'err'
}
}
return {
props: {
newSalonList: subSalonList,
}
}
}
site meta tag
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"><link rel="icon" href="https://kanunu-beauty.com/favicon.ico">
<link rel="apple-touch-icon" href="https://kanunu-beauty.com/favicon.ico">
<title>Kanunu(カヌヌ)|パーソナルカラー・骨格診断・顔タイプ診断などのイメコンサロンが探せる日本最大級の検索サイト</title>
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="#kanunu_official">
<meta name="twitter:creator" content="#kanunu_official">
<meta property="og:image:alt" content="Kanunu_Image">
<meta property="og:image:width" content="800">
<meta property="og:image:height" content="600">
<meta property="og:locale" content="ja_JP"><meta property="og:site_name" content="Kanunu(カヌヌ)"><link rel="icon" href="https://kanunu-beauty.com/favicon.ico"><link rel="apple-touch-icon" href="https://kanunu-beauty.com/favicon.ico" sizes="76x76"><meta name="robots" content="index,follow">
<meta property="og:image" content="https://kanunu-beauty.com/kanunu.png">
<meta name="description" content="パーソナルカラー・骨格診断・顔タイプ診断などのイメコンサロンが探せる日本最大級の検索サイト。24時間いつでもネット検索OK。イメージコンサルティングを検索するならKanunu">
<meta property="og:title" content="Kanunu(カヌヌ)|パーソナルカラー・骨格診断・顔タイプ診断などのイメコンサロンが探せる日本最大級の検索サイト">
<meta property="og:description" content="パーソナルカラー・骨格診断・顔タイプ診断などのイメコンサロンが探せる日本最大級の検索サイト。24時間いつでもネット検索OK。イメージコンサルティングを検索するならKanunu">
<meta property="og:url" content="https://kanunu-beauty.com/">
<meta property="og:type" content="website">
<link rel="canonical" href="https://kanunu-beauty.com/">
<link rel="preload" as="image" imagesrcset="/_next/image?url=%2Fimecon-personal-color.jpeg&w=640&q=75 640w, /_next/image?url=%2Fimecon-personal-color.jpeg&w=750&q=75 750w, /_next/image?url=%2Fimecon-personal-color.jpeg&w=828&q=75 828w, /_next/image?url=%2Fimecon-personal-color.jpeg&w=1080&q=75 1080w, /_next/image?url=%2Fimecon-personal-color.jpeg&w=1200&q=75 1200w, /_next/image?url=%2Fimecon-personal-color.jpeg&w=1920&q=75 1920w, /_next/image?url=%2Fimecon-personal-color.jpeg&w=2048&q=75 2048w, /_next/image?url=%2Fimecon-personal-color.jpeg&w=3840&q=75 3840w" imagesizes="100vw">
<meta name="next-head-count" content="24">
<meta charset="UTF-8">
<meta name="theme-color" content="#ff889e"><script src="https://apis.google.com/_/scs/abc-static/_/js/k=gapi.lb.ja.5EzdJRYYqVI.O/m=gapi_iframes/rt=j/sv=1/d=1/ed=1/rs=AHpOoo_YkgRfk9NA-tU81xmKmFrifcc8Yg/cb=gapi.loaded_0?le=scs" async=""></script><script src="https://partner.googleadservices.com/gampad/cookie.js?domain=kanunu-beauty.com&callback=_gfp_s_&client=ca-pub-3226715501424001&cookie=ID%3Da210217fc7bc19a0-226fbd731ed100ba%3AT%3D1648307385%3ART%3D1648307385%3AS%3DALNI_Mbl4zhbFc5KZH7kiBhF4bogpQHECQ"></script><script src="https://pagead2.googlesyndication.com/pagead/managed/js/adsense/m202205050101/show_ads_impl_fy2019.js?bust=31067451" id="google_shimpl"></script><script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3226715501424001" crossorigin="anonymous" data-checked-head="true"></script><link rel="preload" href="/_next/static/css/3af99d7a8bc648e8.css" as="style"><link rel="stylesheet" href="/_next/static/css/3af99d7a8bc648e8.css" data-n-g=""><noscript data-n-css=""></noscript><script defer="" nomodule="" src="/_next/static/chunks/polyfills-5cd94c89d3acac5f.js"></script><script src="/_next/static/chunks/webpack-5752944655d749a0.js" defer=""></script><script src="/_next/static/chunks/framework-5f4595e5518b5600.js" defer=""></script><script src="/_next/static/chunks/main-958eae9894028eb3.js" defer=""></script><script src="/_next/static/chunks/pages/_app-7066ec57ee9a283b.js" defer=""></script><script src="/_next/static/chunks/675-a0e7bc4ebaaecba0.js" defer=""></script><script src="/_next/static/chunks/pages/index-b8f101c1b5c4520a.js" defer=""></script><script src="/_next/static/c6OFukToh53vEUvjEggAL/_buildManifest.js" defer=""></script><script src="/_next/static/c6OFukToh53vEUvjEggAL/_ssgManifest.js" defer=""></script><script src="/_next/static/c6OFukToh53vEUvjEggAL/_middlewareManifest.js" defer=""></script>
<meta http-equiv="origin-trial" content="AxujKG9INjsZ8/gUq8+dTruNvk7RjZQ1oFhhgQbcTJKDnZfbzSTE81wvC2Hzaf3TW4avA76LTZEMdiedF1vIbA4AAABueyJvcmlnaW4iOiJodHRwczovL2ltYXNkay5nb29nbGVhcGlzLmNvbTo0NDMiLCJmZWF0dXJlIjoiVHJ1c3RUb2tlbnMiLCJleHBpcnkiOjE2NTI3NzQ0MDAsImlzVGhpcmRQYXJ0eSI6dHJ1ZX0=">
<meta http-equiv="origin-trial" content="Azuce85ORtSnWe1MZDTv68qpaW3iHyfL9YbLRy0cwcCZwVnePnOmkUJlG8HGikmOwhZU22dElCcfrfX2HhrBPAkAAAB7eyJvcmlnaW4iOiJodHRwczovL2RvdWJsZWNsaWNrLm5ldDo0NDMiLCJmZWF0dXJlIjoiVHJ1c3RUb2tlbnMiLCJleHBpcnkiOjE2NTI3NzQ0MDAsImlzU3ViZG9tYWluIjp0cnVlLCJpc1RoaXJkUGFydHkiOnRydWV9">
<meta http-equiv="origin-trial" content="A16nvcdeoOAqrJcmjLRpl1I6f3McDD8EfofAYTt/P/H4/AWwB99nxiPp6kA0fXoiZav908Z8etuL16laFPUdfQsAAACBeyJvcmlnaW4iOiJodHRwczovL2dvb2dsZXRhZ3NlcnZpY2VzLmNvbTo0NDMiLCJmZWF0dXJlIjoiVHJ1c3RUb2tlbnMiLCJleHBpcnkiOjE2NTI3NzQ0MDAsImlzU3ViZG9tYWluIjp0cnVlLCJpc1RoaXJkUGFydHkiOnRydWV9">
<meta http-equiv="origin-trial" content="AxBHdr0J44vFBQtZUqX9sjiqf5yWZ/OcHRcRMN3H9TH+t90V/j3ENW6C8+igBZFXMJ7G3Pr8Dd13632aLng42wgAAACBeyJvcmlnaW4iOiJodHRwczovL2dvb2dsZXN5bmRpY2F0aW9uLmNvbTo0NDMiLCJmZWF0dXJlIjoiVHJ1c3RUb2tlbnMiLCJleHBpcnkiOjE2NTI3NzQ0MDAsImlzU3ViZG9tYWluIjp0cnVlLCJpc1RoaXJkUGFydHkiOnRydWV9">
<meta http-equiv="origin-trial" content="A88BWHFjcawUfKU3lIejLoryXoyjooBXLgWmGh+hNcqMK44cugvsI5YZbNarYvi3roc1fYbHA1AVbhAtuHZflgEAAAB2eyJvcmlnaW4iOiJodHRwczovL2dvb2dsZS5jb206NDQzIiwiZmVhdHVyZSI6IlRydXN0VG9rZW5zIiwiZXhwaXJ5IjoxNjUyNzc0NDAwLCJpc1N1YmRvbWFpbiI6dHJ1ZSwiaXNUaGlyZFBhcnR5Ijp0cnVlfQ==">
<meta http-equiv="origin-trial" content="AzoawhTRDevLR66Y6MROu167EDncFPBvcKOaQispTo9ouEt5LvcBjnRFqiAByRT+2cDHG1Yj4dXwpLeIhc98/gIAAACFeyJvcmlnaW4iOiJodHRwczovL2RvdWJsZWNsaWNrLm5ldDo0NDMiLCJmZWF0dXJlIjoiUHJpdmFjeVNhbmRib3hBZHNBUElzIiwiZXhwaXJ5IjoxNjYxMjk5MTk5LCJpc1N1YmRvbWFpbiI6dHJ1ZSwiaXNUaGlyZFBhcnR5Ijp0cnVlfQ==">
<meta http-equiv="origin-trial" content="A6+nc62kbJgC46ypOwRsNW6RkDn2x7tgRh0wp7jb3DtFF7oEhu1hhm4rdZHZ6zXvnKZLlYcBlQUImC4d3kKihAcAAACLeyJvcmlnaW4iOiJodHRwczovL2dvb2dsZXN5bmRpY2F0aW9uLmNvbTo0NDMiLCJmZWF0dXJlIjoiUHJpdmFjeVNhbmRib3hBZHNBUElzIiwiZXhwaXJ5IjoxNjYxMjk5MTk5LCJpc1N1YmRvbWFpbiI6dHJ1ZSwiaXNUaGlyZFBhcnR5Ijp0cnVlfQ==">
<meta http-equiv="origin-trial" content="A/9La288e7MDEU2ifusFnMg1C2Ij6uoa/Z/ylwJIXSsWfK37oESIPbxbt4IU86OGqDEPnNVruUiMjfKo65H/CQwAAACLeyJvcmlnaW4iOiJodHRwczovL2dvb2dsZXRhZ3NlcnZpY2VzLmNvbTo0NDMiLCJmZWF0dXJlIjoiUHJpdmFjeVNhbmRib3hBZHNBUElzIiwiZXhwaXJ5IjoxNjYxMjk5MTk5LCJpc1N1YmRvbWFpbiI6dHJ1ZSwiaXNUaGlyZFBhcnR5Ijp0cnVlfQ=="><link as="script" rel="prefetch" href="/_next/static/chunks/pages/mypage-141f665c92390596.js"><link as="script" rel="prefetch" href="/_next/static/chunks/733-e7d84bed7ed356fa.js"><link as="script" rel="prefetch" href="/_next/static/chunks/pages/bookmark-61fff4ba51ad8155.js"><link as="script" rel="prefetch" href="/_next/static/chunks/866-c05af8e23c872e2c.js"><link as="script" rel="prefetch" href="/_next/static/chunks/pages/signin-ae60a19128cbc932.js"><link as="script" rel="prefetch" href="/_next/static/chunks/932-f01e4d1c2aeb2e91.js"><link as="script" rel="prefetch" href="/_next/static/chunks/pages/area-bf69ae687aa602d8.js"><link as="script" rel="prefetch" href="/_next/static/chunks/pages/tosalons-79196977b12f1828.js"><link as="script" rel="prefetch" href="/_next/static/chunks/pages/salon/%5B%5B...salon%5D%5D-15ea0d621d053ed1.js"><link rel="preload" href="https://adservice.google.co.jp/adsid/integrator.js?domain=kanunu-beauty.com" as="script"><script type="text/javascript" src="https://adservice.google.co.jp/adsid/integrator.js?domain=kanunu-beauty.com"></script><link rel="preload" href="https://adservice.google.com/adsid/integrator.js?domain=kanunu-beauty.com" as="script"><script type="text/javascript" src="https://adservice.google.com/adsid/integrator.js?domain=kanunu-beauty.com"></script><script src="https://apis.google.com/js/api.js?onload=__iframefcb771037" type="text/javascript" charset="UTF-8" gapi_processed="true"></script><link as="script" rel="prefetch" href="/_next/static/chunks/pages/privacy-policy-244018d019bd23ac.js"><link as="script" rel="prefetch" href="/_next/static/chunks/pages/terms-of-service-6bb94404533a0249.js">
</head>
_app.js
import React, { useEffect } from 'react'
import { useRouter } from 'next/router'
import Script from 'next/script'
import Head from 'next/head'
import * as gtag from '../lib/gtag.js'
import { GA_TRACKING_ID } from '../lib/gtag'
import { Provider } from "react-redux"
import redux from '../components/store/redux.js'
import '../../public/styles/Common.scss'
import '../../public/styles/Top.scss'
import '../../public/styles/index.scss'
import '../../public/styles/Header.scss'
import '../../public/styles/Footer.scss'
import '../../public/styles/ToSalons.scss'
import '../../public/styles/Terms.scss'
import '../../public/styles/Area.scss'
import '../../public/styles/Salon.scss'
import '../../public/styles/Search.scss'
import '../../public/styles/Bookmark.scss'
import '../../public/styles/SignUp.scss'
import '../../public/styles/SignUp.scss'
import '../../public/styles/Mypage.scss'
import '../../public/styles/SignUp.scss'
import '../../public/styles/SignUp.scss'
import "../../public/styles/Pagination.scss"
import Header from '../components/block/Header.js'
import Footer from '../components/block/Footer.js'
import { DefaultSeo } from 'next-seo'
import SEO from '../../next-seo.config'
import { config } from '#fortawesome/fontawesome-svg-core'
import '#fortawesome/fontawesome-svg-core/styles.css'
config.autoAddCss = false
export default function App({ Component, pageProps }) {
const router = useRouter()
useEffect(() => {
const handleComplete = (url) => {
// google analyticsの設定
gtag.pageview(url)
};
router.events.on("routeChangeComplete", handleComplete);
router.events.on("routeChangeError", handleComplete);
return () => {
router.events.off('routeChangeComplete', handleComplete)
}
},[router])
return (
<>
<Head>
<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no" />
<link rel="icon" href="https://test.com/favicon.ico" />
<link rel="apple-touch-icon" href="https://test.com/favicon.ico" />
</Head>
<DefaultSeo {...SEO} />
{/* Analytics */}
<Script
async
src={`https://www.googletagmanager.com/gtag/js?id=${GA_TRACKING_ID}`}
/>
<Script
id="analytics"
dangerouslySetInnerHTML={{
__html: `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${GA_TRACKING_ID}', {
page_path: window.location.pathname,
});
`,
}}
/>
<Provider store={redux}>
<Header />
<Component {...pageProps} />
<Footer />
</Provider>
</>
)
}
_document.js
import Document, { Html, Head, Main, NextScript } from 'next/document';
class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx);
return { ...initialProps };
}
render() {
return (
<Html lang="ja">
<Head>
<meta charSet="UTF-8" />
<meta name="theme-color" content="#ff889e" />
{/* google広告 */}
<script async
src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=xxxxxxxxxxx"
crossOrigin="anonymous"></script>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
export default MyDocument;

Custom google fonts with NextJs and tailwindCSS

I would like to use google fonts in my NextJS app. I use tailwindCSS and I already imported reference link in the _document.js Head section . In the tailwind.config file I defined my fontFamily, but when I try to use the custom class it does not apply the font family to the html element. What am I doing wrong?
My _document.js file:
import Document, { Html, Head, Main, NextScript } from "next/document";
class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx);
return { ...initialProps };
}
render() {
return (
<Html>
<Head>
<link
href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap"
rel="stylesheet"
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
export default MyDocument;
tailwind.config file:
const defaultTheme = require("tailwindcss/defaultTheme");
module.exports = {
content: [
"./pages/**/*.{js,ts,jsx,tsx}",
"./components/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {
fontFamily: {
press: ["Press Start 2P", ...defaultTheme.fontFamily.sans],
},
},
},
plugins: [],
};
Text where I want to use the custom font:
<h2 className="font-press text-3xl">
This is a random text with custom google font family Press Start 2P!
</h2>
This is my reference and solution:
_document.js
import Document, { Html, Head, Main, NextScript } from "next/document";
class MyDocument extends Document {
render() {
return (
<Html>
<Head>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link
rel="preconnect"
href="https://fonts.gstatic.com"
crossOrigin="true"
/>
<link
href="https://fonts.googleapis.com/css2?family=Press+Start+2P&family=Syne+Mono&family=Ubuntu+Mono&display=swap"
rel="stylesheet"
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
export default MyDocument;
tailwind.config.js
module.exports = {
content: [
"./pages/**/*.{js,ts,jsx,tsx}",
"./components/**/*.{js,ts,jsx,tsx}",
],
theme: {
fontFamily: {
syne_mono: ["Syne Mono", "monospace"],
press: ["Press Start 2P", "cursive"],
ubuntu: ["Ubuntu Mono", "monospace"],
},
extend: {},
},
plugins: [],
};
index.js (home page)
import Head from "next/head";
export default function Home() {
return (
<div>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<div className="flex items-center justify-center h-screen flex-col gap-5">
<h1 className="text-6xl text-blue-600 p-3">Custom Fonts:</h1>
<h2 className="font-syne_mono text-6xl">Syne Mono, monospace</h2>
<h2 className="font-press text-6xl">Press Start 2P, cursive;</h2>
<h2 className=" font-ubuntu text-6xl">Ubuntu Mono, monospace;</h2>
</div>
</div>
);
}
output:
"next": "12.0.7","react": "17.0.2","tailwindcss": "^3.0.5"
You have to use single quotes around the font name in tailwind.config.js:
// ...
extend: {
fontFamily: {
press: ['"Press Start 2P"', ...defaultTheme.fontFamily.sans],
},
},

2 react components within existing html document

This question has been asked however the solutions don't seem to work and I'm not sure why.
I have two react components that have been compiled and preprocessed to JS files. I reference each file within a script tag within my existing HTML document.
Only 1 component loads. Both work independently. It will only render/load the first script tag.
How do I get both components to render?
Path: index.html
<html>
<head> </head>
<body>
<div id="like_button_container"></div>
<br /><br /><br />
<div id="progress_bar"></div>
<script
src="https://unpkg.com/react#17/umd/react.development.js"
crossorigin
></script>
<script
src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"
crossorigin
></script>
</body>
<!-- Load our React component. -->
<script src="progress_bar.js"></script>
<script src="like_button.js"></script>
</html>
Path: Progressbar.js (this is preprocessed to a JS file)
class ProgressBar extends React.Component {
constructor(props) {
super(props);
this;
}
render() {
return <div>Hello, this is a component</div>;
}
}
const domContainer = document.getElementById("progress_bar");
ReactDOM.render(<ProgressBar />, domContainer);
Path: like_button.js
"use strict";
const e = React.createElement;
class LikeButton extends React.Component {
constructor(props) {
super(props);
this.state = { liked: false };
}
render() {
if (this.state.liked) {
return "You liked this.";
}
return e(
"button",
{ onClick: () => this.setState({ liked: true }) },
"Like"
);
}
}
const domContainer = document.getElementById("like_button_container");
ReactDOM.render(e(LikeButton), domContainer);

Facebook shows <p> tag in gatsby blog post description

When I share a blog post link on facebook, the auto generator preview shows the P tag in the description. How can i fix it? My Seo is very messy on google, dont index all pages and social networks. I need help. I'm following as a guideline from the official Gatsby website.
blog post seo props:
<Seo title={data.post.title}
description={data.post.excerpt} />
Seo component:
import React from 'react'
import PropTypes from 'prop-types'
import { Helmet } from 'react-helmet'
import { useLocation } from '#reach/router'
import { useStaticQuery, graphql } from 'gatsby'
const SEO = ({ title, description, image, article }) => {
const { pathname } = useLocation()
const { site } = useStaticQuery(query)
const {
defaultTitle,
titleTemplate,
defaultDescription,
siteUrl,
defaultImage,
twitterUsername
} = site.siteMetadata
const seo = {
title: title || defaultTitle,
description: description || defaultDescription,
image: `${siteUrl}${image || defaultImage}`,
url: `${siteUrl}${pathname}`
}
return (
<Helmet title={seo.title} titleTemplate={titleTemplate}>
<meta name="description" content={seo.description} />
<meta name="image" content={seo.image}
/>
{seo.url && <meta property="og:url" content={seo.url} />}
{(article ? true : null) && <meta property="og:type" content="article" />}
{seo.title && <meta property="og:title" content={seo.title} />}
{seo.description && (
<meta property="og:description" content={seo.description} />
)}
{seo.image && <meta property="og:image" content={seo.image} />}
<meta name="twitter:card" content="summary_large_image" />
{twitterUsername && (
<meta name="twitter:creator" content={twitterUsername} />
)}
{seo.title && <meta name="twitter:title" content={seo.title} />}
{seo.description && (
<meta name="twitter:description" content={seo.description} />
)}
{seo.image && <meta name="twitter:image" content={seo.image} />}
<html lang="pt-BR" />
</Helmet>
)
}
export default SEO
SEO.propTypes = {
title: PropTypes.string,
description: PropTypes.string,
image: PropTypes.string,
article: PropTypes.bool
}
SEO.defaultProps = {
title: null,
description: null,
image: null,
article: false,
lang: 'pt-BR'
}
const query = graphql`
query SEO {
site {
siteMetadata {
defaultTitle: title
titleTemplate
defaultDescription: description
siteUrl: siteUrl
defaultImage: image
twitterUsername
}
}
}
`

Using react-loadable with renderToNodeStream

I'm new to the react-loadable library. I am wondering if it is possible to use React's renderToNodeStream instead of renderToString on the server.
server index.js:
/* code... */
const appWithRouter = (
<Loadable.Capture report={moduleName => modules.push(moduleName)}>
<Provider store={store}>
<StaticRouter location={req.path} context={context}>
<div>{renderRoutes(Routes)}</div>
</StaticRouter>
</Provider>
</Loadable.Capture>
);
let bundles = getBundles(stats, modules);
const htmlStream = renderToNodeStream(appWithRouter);
res.status(200).write(renderHeader(htmlStream)); /* wrong ??? */
htmlStream.pipe(res, { end: false });
htmlStream.on('end', () => {
res.write( renderFooter(store, bundles) );
res.end();
});
});
Loadable.preloadAll().then(() => {
app.listen(PORT, () => console.log(`listening on port: ${PORT}`));
});
render.js file imported into index.js:
export const renderHeader = (content) => {
console.log('content in renderHeader: ', content);
return `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="/styles.css">
<title>Title</title>
</head>
<body>
<div id="app">${content}</div>`;
}
export const renderFooter = (store, bundles) => {
return `<script>
window.INITIAL_STATE = ${JSON.stringify(store.getState())}
</script>
<script src="/vendor.js"></script>
${bundles.map(bundle => {
return `<script src="/${bundle.file}"></script>`}).join('\\n')}
<script src="/app.js"></script>
</body>
</html>
`;
}
I have not found any examples or patterns the use the renderToNodeStream only renderToString. Is this close or way off? I would like to be able to use renderToNodeStream to get a faster response.

Resources