mirror of
https://github.com/AnimeThemes/animethemes-web.git
synced 2026-07-11 01:24:31 +02:00
* fix(seo): og:title parsing & remove synopsis meta since it's dynamic * cleanup(seo): consistent imports & newlines at eof * Removed viewport meta tag from layout since it's duplicate * siteMetadata => useSiteMeta; inline pageTitle; consistent defaultProps
This commit is contained in:
@@ -1,3 +1,7 @@
|
||||
# General
|
||||
.env
|
||||
.env.*
|
||||
|
||||
# NPM
|
||||
/node_modules
|
||||
|
||||
|
||||
+12
-1
@@ -1,5 +1,6 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const siteConfig = require("./site-config")
|
||||
|
||||
// Load environment configuration
|
||||
require("dotenv").config({
|
||||
@@ -16,8 +17,18 @@ const aliases = fs.readdirSync(path.join(__dirname, "src")).reduce((obj, directo
|
||||
return obj;
|
||||
}, {});
|
||||
|
||||
// If Gatsby already set up a path prefix, use that instead
|
||||
const pathPrefix = process.env.GATSBY_PATH_PREFIX || siteConfig.pathPrefix;
|
||||
|
||||
module.exports = {
|
||||
pathPrefix: process.env.GATSBY_PATH_PREFIX || "/animethemes",
|
||||
pathPrefix: pathPrefix,
|
||||
siteMetadata: {
|
||||
lang: siteConfig.siteLanguage,
|
||||
siteName: siteConfig.siteName,
|
||||
description: siteConfig.description,
|
||||
titleTemplate: `%s · ${siteConfig.siteName}`,
|
||||
siteUrl: path.posix.join((process.env.SITE_URL || siteConfig.rootUrl), pathPrefix)
|
||||
},
|
||||
plugins: [
|
||||
"gatsby-source-animethemes",
|
||||
"gatsby-plugin-styled-components",
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@
|
||||
"gatsby-plugin-fontawesome-css": "^1.0.0",
|
||||
"gatsby-plugin-layout": "^1.3.12",
|
||||
"gatsby-plugin-manifest": "^2.7.0",
|
||||
"gatsby-plugin-react-helmet": "^3.3.12",
|
||||
"gatsby-plugin-react-helmet": "^3.5.0",
|
||||
"gatsby-plugin-root-import": "^2.0.5",
|
||||
"gatsby-plugin-styled-components": "^3.3.12",
|
||||
"node-fetch": "^2.6.1",
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
module.exports = {
|
||||
// Basic site metadata
|
||||
siteName: "AnimeThemes",
|
||||
description: "AnimeThemes is a simple and consistent repository of anime opening and ending themes.",
|
||||
siteLanguage: "en",
|
||||
|
||||
// Prefix for all links (for production builds)
|
||||
pathPrefix: "/animethemes",
|
||||
|
||||
// Site URL without pathPrefix
|
||||
rootUrl: "https://animethemes.github.io"
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import Navigation from "components/navigation";
|
||||
import Container from "components/container";
|
||||
import VideoPlayer from "components/videoPlayer";
|
||||
import PlayerContext from "context/playerContext";
|
||||
import SEO from "components/seo";
|
||||
|
||||
export default function Layout({ children, data }) {
|
||||
const video = data ? data.video : null;
|
||||
@@ -21,11 +22,9 @@ export default function Layout({ children, data }) {
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<PlayerContext.Provider value={{ currentVideo, setCurrentVideo }}>
|
||||
<SEO />
|
||||
<Helmet>
|
||||
<meta charSet="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:wght@400,700&display=swap" rel="stylesheet"/>
|
||||
<title>AnimeThemes</title>
|
||||
</Helmet>
|
||||
<GlobalStyle/>
|
||||
<Navigation/>
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import React from "react"
|
||||
import PropTypes from "prop-types";
|
||||
import {useLocation} from "@reach/router";
|
||||
import {Helmet} from "react-helmet";
|
||||
import useSiteMeta from "hooks/useSiteMeta";
|
||||
|
||||
export default function SEO({title, description, meta = [], lang}) {
|
||||
const {pathname} = useLocation();
|
||||
|
||||
const {
|
||||
description: defaultDescription,
|
||||
lang: defaultLanguage,
|
||||
titleTemplate,
|
||||
siteName,
|
||||
siteUrl
|
||||
} = useSiteMeta();
|
||||
|
||||
const seo = {
|
||||
url: `${siteUrl}${pathname}`,
|
||||
lang: lang || defaultLanguage,
|
||||
title: title || siteName,
|
||||
description: description || defaultDescription
|
||||
}
|
||||
|
||||
return (
|
||||
<Helmet
|
||||
htmlAttributes={{
|
||||
lang: seo.lang
|
||||
}}
|
||||
|
||||
title={seo.title}
|
||||
titleTemplate={title ? titleTemplate : null}
|
||||
meta={[
|
||||
{
|
||||
name: "description",
|
||||
content: seo.description
|
||||
},
|
||||
{
|
||||
property: "og:site_name",
|
||||
content: siteName
|
||||
},
|
||||
{
|
||||
property: "og:title",
|
||||
content: title ? titleTemplate.replace(/%s/g, title) : seo.title
|
||||
},
|
||||
{
|
||||
property: "og:description",
|
||||
content: seo.description
|
||||
}
|
||||
].concat(meta)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
SEO.propTypes = {
|
||||
title: PropTypes.string,
|
||||
description: PropTypes.string,
|
||||
meta: PropTypes.arrayOf(PropTypes.object),
|
||||
lang: PropTypes.string
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import {useStaticQuery, graphql} from "gatsby";
|
||||
|
||||
export default function useSiteMeta() {
|
||||
const {site} = useStaticQuery(query);
|
||||
return site.siteMetadata;
|
||||
}
|
||||
|
||||
export const query = graphql`
|
||||
query SEO {
|
||||
site {
|
||||
siteMetadata {
|
||||
titleTemplate
|
||||
description
|
||||
siteName
|
||||
siteUrl
|
||||
lang
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
@@ -9,6 +9,7 @@ import Title from "components/text/title";
|
||||
import Flex, {FlexItem} from "components/flex";
|
||||
import Switcher from "components/switcher";
|
||||
import Button from "components/button";
|
||||
import SEO from "components/seo";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {faTimes} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
@@ -35,6 +36,11 @@ export default function SearchPage({ location: { pathname, search, hash } }) {
|
||||
const [ searchQuery, setSearchQuery ] = useState(urlParams.get("q") || "");
|
||||
const [ debouncedSearchQuery ] = useDebounce(searchQuery, 500);
|
||||
|
||||
// Generates page title based on search query
|
||||
const pageTitle = searchQuery && searchQuery.trim()
|
||||
? `${searchQuery} - Search`
|
||||
: "Search";
|
||||
|
||||
// Temporary effect to listen for changes to the search query that may be made by the quick search (WIP)
|
||||
useEffect(() => { setSearchQuery(urlParams.get("q")) }, [ urlParams ]);
|
||||
|
||||
@@ -59,6 +65,7 @@ export default function SearchPage({ location: { pathname, search, hash } }) {
|
||||
|
||||
return (
|
||||
<StyledSearchPage>
|
||||
<SEO title={pageTitle} />
|
||||
<Title>Search</Title>
|
||||
<StyledSearchOptions>
|
||||
<FlexItem flex={1}>
|
||||
|
||||
@@ -2,6 +2,7 @@ import styled from "styled-components";
|
||||
import Flex from "components/flex";
|
||||
import Button from "components/button";
|
||||
import Title from "components/text/title";
|
||||
import SEO from "components/seo";
|
||||
import {graphql} from "gatsby";
|
||||
|
||||
const StyledYearPage = styled.div`
|
||||
@@ -13,6 +14,7 @@ const StyledYearPage = styled.div`
|
||||
export default function YearPage({ data: { allAnime } }) {
|
||||
return (
|
||||
<StyledYearPage>
|
||||
<SEO title="Browse by Year"></SEO>
|
||||
{allAnime.groupedByYear.map(({ year }) => (
|
||||
<Flex key={year} alignItems="center" justifyContent="center">
|
||||
<Button to={`/year/${year}`}>
|
||||
|
||||
@@ -11,6 +11,7 @@ import ContainerSidebar from "components/container/sidebar";
|
||||
import CollapseCard from "components/card/collapse";
|
||||
import {fullWidth, gapsColumn} from "styles/mixins";
|
||||
import ThemeSwitcher from "components/switcher/theme";
|
||||
import SEO from "components/seo";
|
||||
|
||||
const StyledAnimePage = styled.div`
|
||||
${gapsColumn("1.5rem")}
|
||||
@@ -76,6 +77,7 @@ export default function AnimeDetailPage({ data: { anime } }) {
|
||||
|
||||
return (
|
||||
<StyledAnimePage>
|
||||
<SEO title={anime.name} />
|
||||
<Title>{anime.name}</Title>
|
||||
<ContainerSidebar sidebar={sidebar}>
|
||||
<Flex gapsColumn="1rem">
|
||||
|
||||
@@ -10,6 +10,7 @@ import Flex from "components/flex";
|
||||
import ContainerSidebar from "components/container/sidebar";
|
||||
import {fullWidth, gapsColumn} from "styles/mixins";
|
||||
import ThemeSearchResultCard from "components/card/searchResult/theme";
|
||||
import SEO from "components/seo";
|
||||
|
||||
const StyledArtistPage = styled.div`
|
||||
${gapsColumn("1.5rem")}
|
||||
@@ -76,6 +77,7 @@ export default function ArtistDetailPage({ data: { artist } }) {
|
||||
|
||||
return (
|
||||
<StyledArtistPage>
|
||||
<SEO title={artist.name} />
|
||||
<Title>{artist.name}</Title>
|
||||
<ContainerSidebar sidebar={sidebar}>
|
||||
<Flex gapsColumn="1rem">
|
||||
|
||||
@@ -7,6 +7,7 @@ import {gapsColumn} from "styles/mixins";
|
||||
import Button from "components/button";
|
||||
import Flex from "components/flex";
|
||||
import Switcher from "components/switcher";
|
||||
import SEO from "components/seo";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {faChevronDown} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
@@ -35,9 +36,12 @@ const StyledYearNext = styled(StyledYear)`
|
||||
export default function SeasonIndexPage({ data: { allAnime }, pageContext: { year, season, yearList, seasonList } }) {
|
||||
const previousYear = yearList.indexOf(year) > 0 ? yearList[yearList.indexOf(year) - 1] : null;
|
||||
const nextYear = yearList.indexOf(year) < yearList.length - 1 ? yearList[yearList.indexOf(year) + 1] : null;
|
||||
|
||||
const pageTitle = season ? `${season} ${year} Anime` : `${year} Anime`;
|
||||
|
||||
return (
|
||||
<StyledPage>
|
||||
<SEO title={pageTitle} />
|
||||
<StyledYearContainer>
|
||||
<StyledYearPrevious>
|
||||
{previousYear && (
|
||||
|
||||
@@ -5,6 +5,7 @@ import ContainerSidebar from "components/container/sidebar";
|
||||
import styled from "styled-components";
|
||||
import {gapsColumn} from "styles/mixins";
|
||||
import AnimeSearchResultCard from "components/card/searchResult/anime";
|
||||
import SEO from "components/seo";
|
||||
import useAniList from "hooks/useAniList";
|
||||
import {graphql} from "gatsby";
|
||||
|
||||
@@ -61,6 +62,7 @@ export default function SeriesDetailPage({ data: { series } }) {
|
||||
|
||||
return (
|
||||
<StyledSeriesPage>
|
||||
<SEO title={series.name} />
|
||||
<Title>{series.name}</Title>
|
||||
<ContainerSidebar sidebar={sidebar}>
|
||||
<Flex gapsColumn="1rem">
|
||||
|
||||
@@ -3,6 +3,7 @@ import Flex, {FlexItem} from "components/flex";
|
||||
import styled from "styled-components";
|
||||
import Text from "components/text";
|
||||
import useAniList from "hooks/useAniList";
|
||||
import useSiteMeta from "hooks/useSiteMeta";
|
||||
import SongTitleWithArtists from "components/utils/songTitleWithArtists";
|
||||
import VideoTags from "components/utils/videoTags";
|
||||
import ThemeEntryTags from "components/utils/themeEntryTags";
|
||||
@@ -11,6 +12,7 @@ import Title from "components/text/title";
|
||||
import VideoButton from "components/button/video";
|
||||
import AnimeSearchResultCard from "components/card/searchResult/anime";
|
||||
import ArtistSearchResultCard from "components/card/searchResult/artist";
|
||||
import SEO from "components/seo";
|
||||
|
||||
const StyledVideoInfo = styled(Flex).attrs({
|
||||
gapsColumn: "2rem"
|
||||
@@ -23,6 +25,7 @@ export default function VideoPage({ data: { video } }) {
|
||||
const theme = entry.theme;
|
||||
const anime = theme.anime;
|
||||
|
||||
const { siteName } = useSiteMeta();
|
||||
const { image } = useAniList(anime);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -52,8 +55,31 @@ export default function VideoPage({ data: { video } }) {
|
||||
};
|
||||
}).filter((otherEntry) => !!otherEntry);
|
||||
|
||||
// Generates and returns page title
|
||||
const pageTitle = entry.version
|
||||
? `${theme.song.title} (${anime.name} ${theme.slug} V${entry.version})`
|
||||
: `${theme.song.title} (${anime.name} ${theme.slug})`;
|
||||
|
||||
const pageDesc = (() => {
|
||||
// Generates and returns page description for SEO
|
||||
let song = theme.song,
|
||||
artistStr = "",
|
||||
version = entry.version ? ` Version ${entry.version}` : "";
|
||||
if (song.performances && song.performances.length) {
|
||||
artistStr = " by ";
|
||||
song.performances.map((performance, index) => {
|
||||
artistStr += performance.as || performance.artist.name;
|
||||
if (index < song.performances.length - 1) {
|
||||
artistStr += (index === song.performances.length - 2 ? ", and " : ", ");
|
||||
}
|
||||
});
|
||||
}
|
||||
return `Watch ${anime.name} ${theme.slug}${version}: ${song.title}${artistStr} on ${siteName}`;
|
||||
})();
|
||||
|
||||
return (
|
||||
<StyledVideoInfo>
|
||||
<SEO title={pageTitle} description={pageDesc} />
|
||||
<Flex row alignItems="center" gapsRow="1rem">
|
||||
<FlexItem flex={1}>
|
||||
<Flex justifyContent="center" gapsColumn="0.25rem">
|
||||
|
||||
Reference in New Issue
Block a user