Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion netlify.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
[[redirects]]
from = "/tmir"
to = "https://tmir.reactiflux.com/"
status = 302
status = 301
force = true

[[plugins]]
Expand Down
3 changes: 3 additions & 0 deletions next-sitemap.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ module.exports = {
generateRobotsTxt: true, // (optional)
priority: 1,
autoLastmod: false,
// The OAuth callback and the unfinished podcast player carry noindex. They
// stay crawlable in robots.txt: Google has to fetch a page to see noindex.
exclude: ["/auth/*", "/podcasts/*", "/404", "/blog/1"],
transform: async (config, url) => {
if (url.includes("/blog/post/")) {
return await transformBlogPost(url);
Expand Down
49 changes: 48 additions & 1 deletion public/_redirects
Original file line number Diff line number Diff line change
@@ -1 +1,48 @@
/transcripts/tmir-204-08 /transcripts/tmir-2024-08
# This Month in React transcripts moved to tmir.reactiflux.com. Each old
# transcript URL redirects permanently to its episode page there; the
# heading anchors are generated with the same slug rule, so #fragments
# still land on the same section.
/transcripts/tmir-2024-01 https://tmir.reactiflux.com/episodes/2024-01 301!
/transcripts/tmir-2024-02 https://tmir.reactiflux.com/episodes/2024-02 301!
/transcripts/tmir-2024-03 https://tmir.reactiflux.com/episodes/2024-03 301!
/transcripts/tmir-2024-04 https://tmir.reactiflux.com/episodes/2024-04 301!
/transcripts/tmir-2024-05 https://tmir.reactiflux.com/episodes/2024-05 301!
/transcripts/tmir-2024-06 https://tmir.reactiflux.com/episodes/2024-06 301!
/transcripts/tmir-2024-07 https://tmir.reactiflux.com/episodes/2024-07 301!
/transcripts/tmir-2024-08 https://tmir.reactiflux.com/episodes/2024-08 301!
/transcripts/tmir-2024-09 https://tmir.reactiflux.com/episodes/2024-09 301!
/transcripts/tmir-2024-10 https://tmir.reactiflux.com/episodes/2024-10 301!
/transcripts/tmir-2024-11 https://tmir.reactiflux.com/episodes/2024-11 301!
/transcripts/tmir-2024-12 https://tmir.reactiflux.com/episodes/2024-12 301!
/transcripts/tmir-2025-01 https://tmir.reactiflux.com/episodes/2025-01 301!
/transcripts/tmir-2025-02 https://tmir.reactiflux.com/episodes/2025-02 301!
/transcripts/tmir-2025-03 https://tmir.reactiflux.com/episodes/2025-03 301!
/transcripts/tmir-2025-04 https://tmir.reactiflux.com/episodes/2025-04 301!
/transcripts/tmir-2025-05 https://tmir.reactiflux.com/episodes/2025-05 301!
/transcripts/tmir-2025-06 https://tmir.reactiflux.com/episodes/2025-06 301!
/transcripts/tmir-2025-07 https://tmir.reactiflux.com/episodes/2025-07 301!
/transcripts/tmir-2025-08 https://tmir.reactiflux.com/episodes/2025-08 301!
/transcripts/tmir-2025-09 https://tmir.reactiflux.com/episodes/2025-09 301!
/transcripts/tmir-2025-10 https://tmir.reactiflux.com/episodes/2025-10 301!
/transcripts/tmir-2025-11 https://tmir.reactiflux.com/episodes/2025-11 301!
/transcripts/tmir-2025-12 https://tmir.reactiflux.com/episodes/2025-12 301!
/transcripts/tmir-2026-01 https://tmir.reactiflux.com/episodes/2026-01 301!
/transcripts/tmir-2026-02 https://tmir.reactiflux.com/episodes/2026-02 301!
/transcripts/tmir-2026-04 https://tmir.reactiflux.com/episodes/2026-04 301!
/transcripts/tmir-2026-05 https://tmir.reactiflux.com/episodes/2026-05 301!
/transcripts/tmir-2026-06 https://tmir.reactiflux.com/episodes/2026-06 301!
/transcripts/tmir-2026-07 https://tmir.reactiflux.com/episodes/2026-07 301!
/transcripts/tmir-2026-08 https://tmir.reactiflux.com/episodes/2026-08 301!
/transcripts/tmir-april-2023 https://tmir.reactiflux.com/episodes/2023-04 301!
/transcripts/tmir-august-2023 https://tmir.reactiflux.com/episodes/2023-08 301!
/transcripts/tmir-dec-2023 https://tmir.reactiflux.com/episodes/2023-12 301!
/transcripts/tmir-june-2023 https://tmir.reactiflux.com/episodes/2023-06 301!
/transcripts/tmir-march-2023 https://tmir.reactiflux.com/episodes/2023-03 301!
/transcripts/tmir-may-2023 https://tmir.reactiflux.com/episodes/2023-05 301!
/transcripts/tmir-nov-2023 https://tmir.reactiflux.com/episodes/2023-11 301!
/transcripts/tmir-october-2023 https://tmir.reactiflux.com/episodes/2023-10 301!
/transcripts/tmir-september-2023 https://tmir.reactiflux.com/episodes/2023-09 301!
/transcripts/tmir-204-08 https://tmir.reactiflux.com/episodes/2024-08 301!

# Page 1 of the blog index is /blog; /blog/1 is built too but never linked.
/blog/1 /blog 301!
9 changes: 8 additions & 1 deletion src/components/Layout/Layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export function Layout({
children,
description,
title,
noindex = false,
structuredData = undefined,
sidebar = false,
...props
}) {
Expand Down Expand Up @@ -55,7 +57,12 @@ export function Layout({
<MarkdownStyles />
<Menu />
<Main largeTitle as={as} {...props} isOpen={isOpen} sidebar={sidebar}>
<SEO title={title} description={description} />
<SEO
title={title}
description={description}
noindex={noindex}
structuredData={structuredData}
/>
{typeof children === "function" ? children(setIsOpen) : children}
{sidebar ? (
<SidebarToggleButton isOpen={isOpen} onClick={toggleIsOpen} fixed />
Expand Down
82 changes: 63 additions & 19 deletions src/components/Layout/SEO.js
Original file line number Diff line number Diff line change
@@ -1,45 +1,89 @@
import React from "react";
import Head from "next/head";
import { useRouter } from "next/router";

import Favicon from "assets/favicon.png";

// This will be broken on previews cuz fuck you it's easy
const banner = "https://reactiflux.com/logo-banner.png";
export const SITE_URL = "https://www.reactiflux.com";
export const SITE_NAME = "Reactiflux";
export const SITE_DESCRIPTION =
"Reactiflux is a Discord community of React and JavaScript developers. Ask questions, get help, talk shop, and join live Q&As and the This Month in React podcast.";

// Absolute and on www, the host the site is served from. Deploy previews still
// point here, which is fine: crawlers only ever see production.
const BANNER = `${SITE_URL}/logo-banner.png`;

/** The URL this page is canonically served at: no query, hash or trailing slash. */
function canonicalUrl(asPath) {
const path = asPath.split(/[?#]/)[0].replace(/(.)\/+$/, "$1");
return `${SITE_URL}${path}`;
}

export function SEO({
description,
image,
meta,
keywords,
title,
noindex,
structuredData,
}) {
const { asPath } = useRouter();
const url = canonicalUrl(asPath);
// Bare titles like "Jobs" or "Transcripts" say nothing in a search result
// or a browser tab full of them, so name the site unless the title already does.
const fullTitle = !title
? SITE_NAME
: title.includes(SITE_NAME)
? title
: `${title} · ${SITE_NAME}`;
const desc = description || SITE_DESCRIPTION;
const img = image || BANNER;

export function SEO({ description, image, lang, meta, keywords, title }) {
return (
<Head>
{[
{ name: `description`, content: description },
{ property: `og:title`, content: title },
{ property: `og:image`, content: banner },
{ property: `og:description`, content: description },
{ name: `description`, content: desc },
{ property: `og:title`, content: fullTitle },
{ property: `og:description`, content: desc },
{ property: `og:url`, content: url },
{ property: `og:site_name`, content: SITE_NAME },
{ property: `og:type`, content: `website` },
{ name: `twitter:card`, content: `summary` },
{ name: `twitter:title`, content: title },
{ name: `twitter:image`, content: banner },
{ name: `twitter:description`, content: description },
{ property: `og:image`, content: img },
{ name: `twitter:card`, content: `summary_large_image` },
{ name: `twitter:site`, content: `@reactiflux` },
{ name: `twitter:title`, content: fullTitle },
{ name: `twitter:description`, content: desc },
{ name: `twitter:image`, content: img },
]
.concat(noindex ? { name: `robots`, content: `noindex` } : [])
.concat(
keywords.length > 0
? { name: `keywords`, content: keywords.join(`, `) }
: [],
)
.concat(image ? { property: `og:image`, content: image } : [])
.concat(meta)
.map((m, index) => (
<meta key={index} {...m} />
.map((m) => (
<meta key={m.name ?? m.property} {...m} />
))}
{[{ rel: "icon", href: Favicon.src, type: "image/png" }].map((x, i) => (
<link key={i} {...x} />
))}
<title>{title}</title>
{/* A noindexed page asking to be canonical sends Google mixed signals. */}
{noindex ? null : <link rel="canonical" href={url} />}
<link rel="icon" href={Favicon.src} type="image/png" />
<title>{fullTitle}</title>
{structuredData ? (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
// `<` escaped so a string in the data can't close the script tag.
__html: JSON.stringify(structuredData).replace(/</g, "\\u003c"),
}}
/>
) : null}
</Head>
);
}

SEO.defaultProps = {
lang: `en`,
meta: [],
keywords: [],
};
1 change: 1 addition & 0 deletions src/md-pages/tips.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
title: Get Better Help
description: "How to ask questions in Reactiflux that get good answers: etiquette, sharing code, and getting the most out of a large developer chat community."
sidebar: true
---

Expand Down
24 changes: 0 additions & 24 deletions src/md-pages/tmir.md

This file was deleted.

8 changes: 7 additions & 1 deletion src/pages/auth/discordcb.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from "react";
import Head from "next/head";

const DiscordCB = () => {
React.useEffect(() => {
Expand Down Expand Up @@ -58,7 +59,12 @@ const DiscordCB = () => {
window.close();
});
});
return null;
// An OAuth popup callback, not a page anyone should land on from search.
return (
<Head>
<meta name="robots" content="noindex" />
</Head>
);
};

export default DiscordCB;
4 changes: 3 additions & 1 deletion src/pages/blog/post/[slug].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@ export default function MarkdownPage({
title,
headings,
sidebar,
description,
}: Awaited<ReturnType<typeof getStaticProps>>["props"]) {
return (
<Layout
title={title}
sidebar={sidebar}
as={undefined}
description={undefined}
description={description}
>
{(setSidebar: any) => (
<>
Expand Down Expand Up @@ -77,6 +78,7 @@ export const getStaticProps = async ({
return {
props: {
...pick(["title", "sidebar"], doc),
description: doc.description ?? null,
headings,
html,
},
Expand Down
24 changes: 22 additions & 2 deletions src/pages/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,21 @@ import {
} from "@assets/logos";
import { ButtonLink, Layout, Link } from "@components";
import { isMobile } from "@utils/theme";
import { SITE_DESCRIPTION, SITE_URL } from "@components/Layout/SEO";

const ORGANIZATION = {
"@context": "https://schema.org",
"@type": "Organization",
name: "Reactiflux",
url: `${SITE_URL}/`,
logo: `${SITE_URL}/logo-banner.png`,
description: SITE_DESCRIPTION,
sameAs: [
"https://discord.gg/reactiflux",
"https://github.com/reactiflux",
"https://twitter.com/reactiflux",
],
};

const P = styled.p`
${(props) =>
Expand All @@ -33,7 +48,12 @@ const P = styled.p`

const Index = ({ userCount }) => {
return (
<Layout title="Welcome" homepage>
<Layout
title="Reactiflux: the React & JavaScript community on Discord"
description={SITE_DESCRIPTION}
structuredData={ORGANIZATION}
homepage
>
<h1>
<span>Welcome to</span>
Reactiflux
Expand Down Expand Up @@ -91,7 +111,7 @@ const Index = ({ userCount }) => {
>
Join Reactiflux
</ButtonLink>
<ButtonLink href="/schedule/" title="Q&A Schedule" secondary>
<ButtonLink href="/schedule" title="Q&A Schedule" secondary>
Q&A Schedule
</ButtonLink>
</P>
Expand Down
7 changes: 6 additions & 1 deletion src/pages/jobs.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ const Jobs = () => {

return (
<QueryClientProvider client={queryClient}>
<Layout title="Jobs" largeTitle sidebar>
<Layout
title="Jobs"
description="React and JavaScript jobs posted by members of the Reactiflux community."
largeTitle
sidebar
>
{(setSidebar) => (
<>
<h1>
Expand Down
14 changes: 14 additions & 0 deletions src/pages/podcasts/[slug].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from "react";
import { z } from "zod";
import { type RootTranscript } from "@utils/transcript-types";
import { useRouter } from "next/router";
import Head from "next/head";
import { Layout } from "@components";

const getNestedProperty = <T extends any>(
Expand Down Expand Up @@ -46,7 +47,20 @@ export const getStaticProps = (t: { params: { slug: Slug } }) => {
};
};

// An unfinished experiment with a hardcoded slug. noindex goes on every render
// path, including the loading state that is all the prerendered HTML holds.
export default function PodPage({ slug }: { slug: Slug }) {
return (
<>
<Head>
<meta name="robots" content="noindex" />
</Head>
<PodPlayer slug={slug} />
</>
);
}

function PodPlayer({ slug }: { slug: Slug }) {
console.log(slug);

const audioRef = useRef<HTMLAudioElement | null>(null);
Expand Down
Loading
Loading