Inertia Link component misbehavior - reactjs

I am using Inertia to create React & Laravel website. The Inertia Link component pops up a stack of the page visited instead of rendering the page as expected. What could be the issue?
Initially it worked just fine. Here's my Header component.
import React, { useState } from "react";
import { Link } from "#inertiajs/inertia-react";
import MenuIcon from "#mui/icons-material/Menu";
import CloseIcon from "#mui/icons-material/Close";
import logo from "../../../../../public/images/zawadiz.png";
import "./Header.css";
const Header = () => {
const [showMobileMenu, setShowMobile] = useState(false);
return (
<header>
<div className="container">
<Link href="/" className="brand">
<img src={logo} alt="" className="logo" width={130} />
</Link>
<div
className="menu"
style={{
right: showMobileMenu ? 0 : "-100%",
transition: "all .5s ease",
}}
>
<Link href="/">Home</Link>
<Link href="/about">About</Link>
<Link href="/services">Services</Link>
<Link href="/products">Products</Link>
<Link href="/contact">Contact</Link>
</div>
<div className="menu-icon">
{showMobileMenu ? (
<CloseIcon
onClick={() => setShowMobile(!showMobileMenu)}
/>
) : (
<MenuIcon
onClick={() => setShowMobile(!showMobileMenu)}
/>
)}
</div>
</div>
</header>
);
};
export default Header;
app.blade.php
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
<link rel="icon" type="image/svg+xml" href="../../public/images/favicon.png" />
<link href="{{ mix('/css/app.css') }}" rel="stylesheet" />
<script src="{{ mix('/js/app.js') }}" defer></script>
#inertiaHead
</head>
<body>
#inertia
</body>
</html>
Please see the screenshot below for the behavior experienced.
Thanks in advance.

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],
},
},

Failed to Export Default and Another Function

Plan - To render <List /> element in index.js. Displays the todo items the user has created.
Error -
./src/components/App.jsx
Attempted import error: './List' does not contain a default export (imported as 'List').
index.js -
import React from 'react';
import ReactDOM from 'react-dom';
import {List, Render} from './components/List';
import App from './components/App';
import '../src/styles.css';
ReactDOM.render(
<App />,
document.getElementById('root')
);
ReactDOM.render(
<Render />
, document.getElementById("list"));
List.jsx -
import react, { useRef } from 'react';
import ReactDOM from 'react-dom';
var todoItems = [];
const inputRef = useRef();
function onClick() {
todoItems.push(inputRef.current.value);
console.log("Pushed item in the array!");
render(inputRef.current.value);
}
function Render(value) {
todoItems.forEach(function a(item) {
<h1>{item}</h1>
});
}
function List() {
return (
<div className="mainbox">
<div className="inputdiv">
<input
type="text"
ref={inputRef}
placeholder="Enter Task..."
className="textbox"
id="taskName"
/>
<button className="button" onClick={onClick}>+</button>
</div>
</div>
);
}
export {List, Render};
I also tried -
export default List;
export {Render};
But it says useRef() cannot be called at the top level.
So I moved the inputRef to the List(), but it says that Render isn't defined.
Thanks!
P.S
After this import/export problem is solved, will the <h1> display?
function Render(value) {
todoItems.forEach(function a(item) {
<h1>{item}</h1>
});
}
EDIT -
index.html -
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link font-family: "Montserrat" , sans-serif;
href="https://fonts.googleapis.com/css?family=McLaren|Montserrat&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="../src/styles.css">
<title>Mandy's Todo-List App!</title>
</head>
<body>
<div id="root">
<div id="list">
</div>
</div>
</body>
</html>
import { useState, useEffect } from 'react';
function App() {
const [value, setValue] = useState('');
const [list, setList] = useState([]);
function handleChange(event) {
setValue(event.target.value);
}
function addTodo() {
setList([...list, value]);
}
useEffect(() => setValue(''), [list]);
return (
<div className='App'>
<input value={value} onChange={handleChange}/>
<button onClick={addTodo}>Add</button>
{list.map((item, index) => <h1 key={index}>{item}</h1>)}
</div>
);
}
export default App;

React Redux Connect wrap component

Im learning react and the instructor creates components as in Code 1 where as I create the function as in Code 2. How do we wrap code 2 within Redux Connect()?
Code 1
const Header = ({ currentUser }) => (
<div className='header'>
<Link className='logo-container' to='/'>
<Logo className='logo' />
</Link>
</div>
);
const mapStateToProps = state => ({
currentUser: state.user.currentUser
});
export default connect(mapStateToProps)(Header);
Code 2
import React from 'react';
import {connect} from 'react-redux';
export default function Header({currentUser}) {
return (
<div className='header'>
<Link className='logo-container' to="/">
<Logo className='logo' />
</Link>
<div className='options'>
<Link className='option' to="/shop">
SHOP
</Link>
<Link className='option' to="/shop">
CONTACT
</Link>
{
currentUser ?
<div className='option' onClick={() => auth.signOut()}>SIGN OUT</div>
:
<Link className='option' to="/signin">SIGN IN</Link>
}
</div>
</div>
)
}
I believe this should work:
import React from 'react';
import {connect} from 'react-redux';
export default connect(mapStateToProps)(function({currentUser})) {
return (
<div className='header'>
<Link className='logo-container' to="/">
<Logo className='logo' />
</Link>
<div className='options'>
<Link className='option' to="/shop">
SHOP
</Link>
<Link className='option' to="/shop">
CONTACT
</Link>
{
currentUser ?
<div className='option' onClick={() => auth.signOut()}>SIGN OUT</div>
:
<Link className='option' to="/signin">SIGN IN</Link>
}
</div>
</div>
)
}
Then, you can import it at any file using this line:
import AnyNameYouWant from './Header.js';
If we would have exported it like this:
export const Header = connect(mapStateToProps)(function({currentUser})) {...
Then you would have needed to change your import statements to this:
import { Header } from './Header.js';
Hope it helps.

React.createElement: type is invalid when using multiple Helmet React Typescript

I have a seo component which gives me React.createElement error, i have been struggling to get it work from last 2 days, can someone please help me to solve this issue
SeoModule.tsx
import * as React from 'react';
import Helmet from 'react-helmet';
import FacebookSeo from './FacebookSeo';
import TwitterSeo from './TwitterSeo';
type propTypes = {
...
}
export default class SeoModule extends React.Component<propTypes> {
render() {
return (
<>
<Helmet title = {seo.title}>
<script type="application/ld+json">{
JSON.stringify(structuredData)
}</script>
</Helmet>
<FacebookSeo
desc={seo.desc}
image={seo.image}
title={seo.title}
type={event ? 'event' : null}
url={seo.url}
/>
<TwitterSeo
type={event ? 'summary_large_image' : null}
title={seo.title}
image={seo.image}
desc={seo.desc}
username={twitter}
/>
</>
)
}
}
and FacebookSeo.tsx
import * as React from 'react';
import Helmet from 'react-helmet';
type propTypes = {
...
};
export default class FacebookSeo extends React.Component<propTypes> {
render() {
const {url, type, title, desc, image} = this.props;
return (
<Helmet>
{url && <meta property="og:url" content={url} />}
{type && <meta property="og:type" content={type} />}
{title && <meta property="og:title" content={title} />}
{desc && <meta property="og:description" content={desc} />}
{image && <meta property="og:image" content={image} />}
</Helmet>
);
}
}
and TwitterSeo.tsx
import * as React from 'react';
import Helmet from 'react-helmet';
type propTypes = {
...
};
export default class TwitterSeo extends React.Component<propTypes> {
render() {
const {username, type, title, desc, image} = this.props;
return (
<Helmet>
<meta name="twitter:card" content={type} />
{username && <meta name="twitter:creator" content={username} />}
{title && <meta name="twitter:title" content={title} />}
{desc && <meta name="twitter:description" content={desc} />}
{image && <meta name="twitter:image" content={image} />}
</Helmet>
);
}
}
But the above code gives me an error
React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in.
Check the render method of SeoModule.
6 | export default (props) =>
Answer:
export default class FacebookSeo extends React.Component<propTypes> {
render() {
const {url, type, title, desc, image} = this.props;
return (
<div>
<Helmet>
{url && <meta property="og:url" content={url} />}
{type && <meta property="og:type" content={type} />}
{title && <meta property="og:title" content={title} />}
{desc && <meta property="og:description" content={desc} />}
{image && <meta property="og:image" content={image} />}
</Helmet>
</div>
);
}
}
Wrap your rendered code in div tags as the Helmet code need to be imbedded into a div or HTML element

Resources