diff --git a/next.config.js b/next.config.js index 2ba49f61..c614282f 100644 --- a/next.config.js +++ b/next.config.js @@ -13,4 +13,17 @@ module.exports = { includePaths: [path.join(__dirname, 'src/styles')], prependData: `@import "common.scss";`, }, + async headers() { + return [ + { + source: '/(.*)', + headers: [ + { + key: 'Strict-Transport-Security', + value: 'max-age=63072000; includeSubDomains; preload', + }, + ], + }, + ]; + }, }; diff --git a/public/fingerprintDefaultMetaImage.png b/public/fingerprintDefaultMetaImage.png new file mode 100644 index 00000000..a54590af Binary files /dev/null and b/public/fingerprintDefaultMetaImage.png differ diff --git a/src/client/components/common/UseCaseWrapper/UseCaseWrapper.tsx b/src/client/components/common/UseCaseWrapper/UseCaseWrapper.tsx index 5ce7dd33..68c43d9c 100644 --- a/src/client/components/common/UseCaseWrapper/UseCaseWrapper.tsx +++ b/src/client/components/common/UseCaseWrapper/UseCaseWrapper.tsx @@ -11,6 +11,7 @@ import RestartIcon from '../../../img/restart.svg'; import { useReset } from '../../../hooks/useReset/useReset'; import classNames from 'classnames'; import { RestartHint } from './RestartHint'; +import { SEO } from '../seo'; type UseCaseWrapperProps = { useCase: Partial; @@ -36,6 +37,7 @@ export const UseCaseWrapper: FunctionComponent = ({ return ( <> + {embed && shouldDisplayResetButton && (
ReactNode))[]; @@ -29,6 +33,7 @@ export type UseCase = { export const USE_CASES = { couponFraud: { title: 'Coupon Fraud', + titleMeta: 'Fingerprint Use Cases | Coupon Promo Fraud and Abuse Live Demo', url: '/coupon-fraud', iconSvg: CouponFraudIcon, descriptionHomepage: [ @@ -43,6 +48,8 @@ export const USE_CASES = { ], description: 'Use the demo below to see how Fingerprint can help identify fraudsters who repeatedly use the same coupon under different scenarios to gain unauthorized benefits.', + descriptionMeta: + 'See in real-time how Fingerprint can stop and prevent coupon promotional abuse. Try out our coupon fraud demo and learn how we safeguard your promotions.', articleUrl: 'https://fingerprint.com/use-cases/coupon-promo-abuse/', instructions: [ <> @@ -73,6 +80,7 @@ export const USE_CASES = { }, credentialStuffing: { title: 'Credential Stuffing', + titleMeta: 'Fingerprint Use Cases | Credential Stuffing Live Demo', url: '/credential-stuffing', articleUrl: 'https://fingerprint.com/use-cases/credential-stuffing/', instructions: [ @@ -110,6 +118,8 @@ export const USE_CASES = {

), + descriptionMeta: + 'See in real-time how Fingerprint can stop credential stuffing preventing unauthorized account access before it happens. Try out our live demo to learn how.', moreResources: [ { type: 'Use case tutorial', @@ -125,6 +135,7 @@ export const USE_CASES = { }, loanRisk: { title: 'Loan Risk', + titleMeta: 'Fingerprint Use Cases | Loan Fraud Live Demo', url: '/loan-risk', iconSvg: LoanRiskIcon, descriptionHomepage: [ @@ -138,6 +149,8 @@ export const USE_CASES = { ], description: 'Use this demo to see how Fingerprint allows you to collect high-quality, low-risk loan applications from your anonymous visitors. Prevent fraudsters and rejected applicants from submitting multiple inconsistent applications.', + descriptionMeta: + 'See in real-time how Fingerprint can stop and prevent fraudsters and rejected applicants from submitting multiple inconsistent applications. Try out our live demo to learn how.', instructions: [ <>Pick some values in the form and submit your loan application., <> @@ -161,6 +174,7 @@ export const USE_CASES = { }, paymentFraud: { title: 'Payment Fraud', + titleMeta: 'Fingerprint Use Cases | Payment Fraud Live Demo', url: '/payment-fraud', articleUrl: 'https://fingerprint.com/use-cases/payment-fraud/', iconSvg: PaymentFraudIcon, @@ -173,6 +187,8 @@ export const USE_CASES = { ], description: 'Use the demo to see how Fingerprint can protect your transactions from card cracking attempts, stolen credit cards, and reduce chargebacks.', + descriptionMeta: + 'See in real-time how Fingerprint can stop and prevent online payment fraud. Try out our live demo to see how Fingerprint can protect your transactions from card cracking attempts, stolen credit cards, and reduce chargebacks.', doNotMentionResetButton: true, instructions: [ <>Change the pre-filled card details to simulate testing stolen credit cards., @@ -209,6 +225,7 @@ export const USE_CASES = { }, paywall: { title: 'Paywall', + titleMeta: 'Fingerprint Use Cases | Content Paywall Live Demo', url: '/paywall', iconSvg: PaywallIcon, descriptionHomepage: [ @@ -220,6 +237,8 @@ export const USE_CASES = { ], description: 'Use the demo below to see how Fingerprint protects your content from users trying to circumvent your paywall. ', + descriptionMeta: + 'See in real-time how Fingerprint can stop and prevent content paywall evasion. Try out our live demo to see how Fingerprint protects your content from users trying to circumvent your paywall.', instructions: [ <>Browse the articles below and open a few of them., <>You will hit a paywall when opening your third article., @@ -235,6 +254,7 @@ export const USE_CASES = { }, personalization: { title: 'Personalization', + titleMeta: 'Fingerprint Use Cases | eCommerce Personalization Live Demo', url: '/personalization', articleUrl: 'https://fingerprint.com/use-cases/personalization/', iconSvg: PersonalizationIcon, @@ -260,6 +280,8 @@ export const USE_CASES = {

), + descriptionMeta: + 'See in real-time how Fingerprint can help you provide your visitors a tailored experience without having to create an account. Try out our live demo to see how user personalization works with Fingerprint.', instructions: [ <>Search for new products., <>Add some items to your cart., @@ -278,6 +300,7 @@ export const USE_CASES = { }, webScraping: { title: 'Web Scraping Prevention', + titleMeta: 'Fingerprint Use Cases | Content Scraping Prevention Live Demo', url: '/web-scraping', articleUrl: 'https://fingerprint.com/use-cases/web-scraping-prevention/', iconSvg: ScrapingIcon, @@ -309,6 +332,8 @@ export const USE_CASES = {

), + descriptionMeta: + 'See in real-time how Fingerprint can stop and prevent content scraping bots. Try out our live demo to see how Fingerprint’s bot detection identifies and blocks malicious bots, and prevents unauthorized data extraction.', doNotMentionResetButton: true, instructions: [ <>Use a normal browser and search for flights., @@ -346,7 +371,10 @@ export const USE_CASES = { }, } as const satisfies Record; -export const PLAYGROUND_METADATA: Pick = { +export const PLAYGROUND_METADATA: Pick< + UseCase, + 'title' | 'url' | 'descriptionHomepage' | 'iconSvg' | 'titleMeta' | 'descriptionMeta' +> = { title: 'Smart Signals', url: '/playground', iconSvg: SmartSignalsIcon, @@ -357,6 +385,8 @@ export const PLAYGROUND_METADATA: Pick, ], + titleMeta: 'Fingerprint Use Cases | Smart Signals Playground', + descriptionMeta: 'Analyze your browser with Fingerprint Pro and see all the available signals.', }; export const USE_CASES_ARRAY = Object.values(USE_CASES); diff --git a/src/client/components/common/seo.tsx b/src/client/components/common/seo.tsx new file mode 100644 index 00000000..943adfd3 --- /dev/null +++ b/src/client/components/common/seo.tsx @@ -0,0 +1,35 @@ +import Head from 'next/head'; +import { FunctionComponent } from 'react'; +import { PRODUCTION_URL } from './content'; + +type HeadSEOProps = { + title: string; + description: string; + image?: string; + path?: string; +}; + +export const SEO: FunctionComponent = ({ title, description, image, path }) => { + const metaImage = image ?? `${PRODUCTION_URL}/fingerprintDefaultMetaImage.png`; + const metaUrl = `${PRODUCTION_URL}${path ?? ''}`; + return ( + + {title} + + + + + + + + + + + + + + + + + ); +}; diff --git a/src/pages/index.module.scss b/src/pages/index.module.scss index e5a4c4e6..62dbf9df 100644 --- a/src/pages/index.module.scss +++ b/src/pages/index.module.scss @@ -66,6 +66,7 @@ border: 1px solid v('gray-box-stroke'); overflow: hidden; color: unset; + cursor: pointer; @include shadowMedium(); @include transition((box-shadow, border)); @@ -91,6 +92,7 @@ font-weight: 600; line-height: 130%; margin-bottom: rem(8px); + display: block; } .useCaseDescription { diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 09afdd08..a07d964b 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -7,10 +7,18 @@ import LinkArrow from '../client/img/externalLinkArrow.svg'; import Image from 'next/image'; import { TEST_IDS } from '../client/e2eTestIDs'; import { Fragment } from 'react'; +import { SEO } from '../client/components/common/seo'; +import { useRouter } from 'next/router'; export default function Index() { + const router = useRouter(); return ( <> +

Fingerprint use cases

@@ -27,12 +35,12 @@ export default function Index() {
{HOMEPAGE_CARDS.map((card) => ( - +
router.push(card.url)}>
-

+ {card.title} -

+
{card.descriptionHomepage.map((line, i) => ( {line} @@ -45,7 +53,7 @@ export default function Index() {
- +
))}
diff --git a/src/pages/playground/index.tsx b/src/pages/playground/index.tsx index 67d0bc2d..441e8c6d 100644 --- a/src/pages/playground/index.tsx +++ b/src/pages/playground/index.tsx @@ -29,6 +29,7 @@ import { usePlaygroundSignals } from '../../client/components/playground/usePlay import { getLocationName } from '../../shared/utils/getLocationName'; import { PLAYGROUND_TAG } from '../../client/components/playground/playgroundTags'; import { CustomPageProps } from '../_app'; +import { PLAYGROUND_METADATA } from '../../client/components/common/content'; // Map cannot be server-side rendered const Map = dynamic(() => import('../../client/components/playground/Map'), { ssr: false }); @@ -404,6 +405,7 @@ export default function PlaygroundPage({ embed }: CustomPageProps) { return ( Analyze your browser with Fingerprint Pro and see all the available signals.

, doNotMentionResetButton: true, diff --git a/src/pages/sitemap.xml.ts b/src/pages/sitemap.xml.ts new file mode 100644 index 00000000..7b85d955 --- /dev/null +++ b/src/pages/sitemap.xml.ts @@ -0,0 +1,37 @@ +import { HOMEPAGE_CARDS, PRODUCTION_URL } from '../client/components/common/content'; + +function generateSiteMap() { + return ` + + + ${PRODUCTION_URL} + + ${HOMEPAGE_CARDS.map(({ url }) => { + return ` + + ${`${PRODUCTION_URL}${url}`} + + `; + }).join('')} + + `; +} + +function SiteMap() { + // getServerSideProps will do the heavy lifting +} + +export async function getServerSideProps({ res }) { + const sitemap = generateSiteMap(); + + res.setHeader('Content-Type', 'text/xml'); + // we send the XML to the browser + res.write(sitemap); + res.end(); + + return { + props: {}, + }; +} + +export default SiteMap;