Skip to content

Commit

Permalink
Feat/seo improvements INTER-306 (#92)
Browse files Browse the repository at this point in the history
* feat: add HSTS header

* feat: add sitemap

* feat: use fingerprinthub for sitemap for now

* feat: add seo component

* feat: rename to seo component

* chore: fix meta url

* chore: improve card accessibility

* chore: add use case seo
  • Loading branch information
JuroUhlar authored Oct 9, 2023
1 parent a41c3b9 commit 4ccaa14
Show file tree
Hide file tree
Showing 9 changed files with 134 additions and 5 deletions.
13 changes: 13 additions & 0 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
},
],
},
];
},
};
Binary file added public/fingerprintDefaultMetaImage.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -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<UseCase>;
Expand All @@ -36,6 +37,7 @@ export const UseCaseWrapper: FunctionComponent<UseCaseWrapperProps> = ({

return (
<>
<SEO title={useCase.titleMeta} description={useCase.descriptionMeta} path={useCase.url} />
{embed && shouldDisplayResetButton && (
<Tooltip title="Click Restart to remove all information obtained from this browser. This will reenable some scenarios for you if you were locked out of a specific action.">
<div
Expand Down
32 changes: 31 additions & 1 deletion src/client/components/common/content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,15 @@ import ScrapingIcon from '../../img/scrapingIcon.svg';
import { ReactNode } from 'react';
import { RestartHint, RestartHintProps } from './UseCaseWrapper/RestartHint';

export const PRODUCTION_URL = 'https://fingerprinthub.com';

export type UseCase = {
title: string;
titleMeta: string;
url: string;
description?: React.ReactNode;
descriptionHomepage?: readonly React.ReactNode[];
descriptionMeta: string;
articleUrl?: string;
doNotMentionResetButton?: boolean;
instructions: readonly (ReactNode | ((props: RestartHintProps) => ReactNode))[];
Expand All @@ -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: [
Expand All @@ -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: [
<>
Expand Down Expand Up @@ -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: [
Expand Down Expand Up @@ -110,6 +118,8 @@ export const USE_CASES = {
</p>
</>
),
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',
Expand All @@ -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: [
Expand All @@ -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.</>,
<>
Expand All @@ -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,
Expand All @@ -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.</>,
Expand Down Expand Up @@ -209,6 +225,7 @@ export const USE_CASES = {
},
paywall: {
title: 'Paywall',
titleMeta: 'Fingerprint Use Cases | Content Paywall Live Demo',
url: '/paywall',
iconSvg: PaywallIcon,
descriptionHomepage: [
Expand All @@ -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.</>,
Expand All @@ -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,
Expand All @@ -260,6 +280,8 @@ export const USE_CASES = {
</p>
</>
),
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.</>,
Expand All @@ -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,
Expand Down Expand Up @@ -309,6 +332,8 @@ export const USE_CASES = {
</p>
</>
),
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.</>,
Expand Down Expand Up @@ -346,7 +371,10 @@ export const USE_CASES = {
},
} as const satisfies Record<string, UseCase>;

export const PLAYGROUND_METADATA: Pick<UseCase, 'title' | 'url' | 'descriptionHomepage' | 'iconSvg'> = {
export const PLAYGROUND_METADATA: Pick<
UseCase,
'title' | 'url' | 'descriptionHomepage' | 'iconSvg' | 'titleMeta' | 'descriptionMeta'
> = {
title: 'Smart Signals',
url: '/playground',
iconSvg: SmartSignalsIcon,
Expand All @@ -357,6 +385,8 @@ export const PLAYGROUND_METADATA: Pick<UseCase, 'title' | 'url' | 'descriptionHo
VPN detection, browser tampering detection, IP blocklist matching, and more.
</p>,
],
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);
Expand Down
35 changes: 35 additions & 0 deletions src/client/components/common/seo.tsx
Original file line number Diff line number Diff line change
@@ -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<HeadSEOProps> = ({ title, description, image, path }) => {
const metaImage = image ?? `${PRODUCTION_URL}/fingerprintDefaultMetaImage.png`;
const metaUrl = `${PRODUCTION_URL}${path ?? ''}`;
return (
<Head>
<title>{title}</title>
<meta name="description" content={description} />
<meta name="image" content={metaImage} />

<meta property="og:type" content="website" />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:site_name" content="Fingerprint Use Cases" />
<meta property="og:image" content={metaImage} />
<meta property="og:url" content={metaUrl} />

<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image" content={metaImage} />
<meta name="twitter:url" content={metaUrl} />
</Head>
);
};
2 changes: 2 additions & 0 deletions src/pages/index.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
border: 1px solid v('gray-box-stroke');
overflow: hidden;
color: unset;
cursor: pointer;

@include shadowMedium();
@include transition((box-shadow, border));
Expand All @@ -91,6 +92,7 @@
font-weight: 600;
line-height: 130%;
margin-bottom: rem(8px);
display: block;
}

.useCaseDescription {
Expand Down
16 changes: 12 additions & 4 deletions src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<>
<SEO
title="Fingerprint Use Cases | Discover Device Intelligence Use Cases"
description={`Explore an extensive range of use cases supported by Fingerprint, and learn how to successfully implement it for your
business with practical guidance and a comprehensive demo.`}
/>
<Container size="large" className={styles.hero}>
<h1 className={styles.title}>Fingerprint use cases</h1>
<div className={styles.intro}>
Expand All @@ -27,12 +35,12 @@ export default function Index() {
</Container>
<div className={styles.useCaseGrid}>
{HOMEPAGE_CARDS.map((card) => (
<Link className={styles.useCaseCard} href={card.url} key={card.url}>
<div className={styles.useCaseCard} key={card.url} onClick={() => router.push(card.url)}>
<div>
<Image src={card.iconSvg} alt="" className={styles.useCaseIcon} />
<h3 className={styles.useCaseTitle} data-test={TEST_IDS.homepageCard.useCaseTitle}>
<Link className={styles.useCaseTitle} data-test={TEST_IDS.homepageCard.useCaseTitle} href={card.url}>
{card.title}
</h3>
</Link>
<div className={styles.useCaseDescription}>
{card.descriptionHomepage.map((line, i) => (
<Fragment key={i}>{line}</Fragment>
Expand All @@ -45,7 +53,7 @@ export default function Index() {
<Image src={LinkArrow} alt="" />
</span>
</div>
</Link>
</div>
))}
</div>
</>
Expand Down
2 changes: 2 additions & 0 deletions src/pages/playground/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
Expand Down Expand Up @@ -404,6 +405,7 @@ export default function PlaygroundPage({ embed }: CustomPageProps) {
return (
<UseCaseWrapper
useCase={{
...PLAYGROUND_METADATA,
title: 'Fingerprint Pro Playground',
description: <p>Analyze your browser with Fingerprint Pro and see all the available signals.</p>,
doNotMentionResetButton: true,
Expand Down
37 changes: 37 additions & 0 deletions src/pages/sitemap.xml.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { HOMEPAGE_CARDS, PRODUCTION_URL } from '../client/components/common/content';

function generateSiteMap() {
return `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>${PRODUCTION_URL}</loc>
</url>
${HOMEPAGE_CARDS.map(({ url }) => {
return `
<url>
<loc>${`${PRODUCTION_URL}${url}`}</loc>
</url>
`;
}).join('')}
</urlset>
`;
}

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;

0 comments on commit 4ccaa14

Please sign in to comment.