В этом руководстве рассказывается, как создать целевую страницу для запущенного приложения SaaS с помощью React и Typescript.


Введение

Пользовательский интерфейс чакры — это библиотека компонентов пользовательского интерфейса, которая очень проста в использовании, имеет доступность из коробки и, самое главное, выглядит невероятно.

В этом руководстве мы создадим целевую страницу для продукта SaaS с помощью:

  1. Раздел Hero — Зацепит читателя. Подробно о товаре
  2. Демонстрационное видео — Как выглядит продукт в действии
  3. Логотипы компаний — создатель доверия, показывающий, что продукт уже нравится другим компаниям.
  4. Возможности — 3 основных способа, которыми продукт помогает пользователям
  5. Основные моменты — Второстепенные функции, которые приятно иметь
  6. Цены
  7. Часто задаваемые вопросы
  8. CTA — напомните читателям зарегистрироваться после того, как они прочитают все о продукте.
  9. Нижний колонтитул


Шаг 1 — Настройте проект React

npm create vite@latest saasbase-chakraui-landing-page -- --template react-ts

cd saasbase-chakraui-landing-page
npm install
npm run dev
Войти в полноэкранный режим

Выйти из полноэкранного режима


Шаг 2. Настройте интерфейс чакры.

Chakra UI — это фантастический набор пользовательского интерфейса, который поставляется с простыми в использовании внешними компонентами, которые также хорошо выглядят.

npm i @chakra-ui/react @emotion/react @emotion/styled framer-motion @chakra-ui/icons
Войти в полноэкранный режим

Выйти из полноэкранного режима

Мы можем настроить его, обернув все наше приложение ChakraUIProvider.

Установите шрифт Inter с помощью:

npm i @fontsource/inter
Войти в полноэкранный режим

Выйти из полноэкранного режима

Редактировать src/main.tsx

import { ChakraProvider, extendTheme } from "@chakra-ui/react";
import React from "react";
import ReactDOM from "react-dom/client";
import { App } from "./App";

const theme = extendTheme({
  colors: {
    brand: {
      50: "#f0e4ff",
      100: "#cbb2ff",
      200: "#a480ff",
      300: "#7a4dff",
      400: "#641bfe",
      500: "#5a01e5",
      600: "#5200b3",
      700: "#430081",
      800: "#2d004f",
      900: "#14001f",
    },
  },
  fonts: {
    heading: `'Inter', sans-serif`,
    body: `'Inter', sans-serif`,
  },
});

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <ChakraProvider theme={theme}>
      <App />
    </ChakraProvider>
  </React.StrictMode>
);
Войти в полноэкранный режим

Выйти из полноэкранного режима


Шаг 3 — Создайте раздел «Герой»

Главный раздел вашей целевой страницы — это самый важный элемент верхней части страницы, который вы можете иметь. Заголовок и описание должны зацепить читателя, чтобы он узнал больше о вашем предложении в течение 4-5 секунд.

Раздел «Герой» будет иметь заголовок, описание и сильный призыв к действию, чтобы у читателей потекли слюнки. Создание доверия, которое показывает, что другие уже используют ваш продукт, имеет большое значение.

Раздел героев

Создать новый файл src/components/HeroSection.tsx :

import {
  Button,
  Center,
  Container,
  Heading,
  Text,
  VStack,
} from "@chakra-ui/react";
import { FunctionComponent } from "react";

interface HeroSectionProps {}

export const HeroSection: FunctionComponent<HeroSectionProps> = () => {
  return (
    <Container maxW="container.lg">
      <Center p={4} minHeight="70vh">
        <VStack>
          <Container maxW="container.md" textAlign="center">
            <Heading size="2xl" mb={4} color="gray.700">
              You don't have to chase your clients around to get paid
            </Heading>

            <Text fontSize="xl" color="gray.500">
              Freelancers use Biller to accept payments and send invoices to
              clients with a single click
            </Text>

            <Button
              mt={8}
              colorScheme="brand"
              onClick={() => {
                window.open(" "_blank");
              }}
            >
              I need this for $10/month →
            </Button>

            <Text my={2} fontSize="sm" color="gray.500">
              102+ builders have signed up in the last 30 days
            </Text>
          </Container>
        </VStack>
      </Center>
    </Container>
  );
};
Войти в полноэкранный режим

Выйти из полноэкранного режима

Обновите src/App.tsx с:

import { Box } from "@chakra-ui/react";
import { HeroSection } from "./components/HeroSection";

export const App = () => {
  return (
    <Box bg="gray.50">
      <HeroSection />
    </Box>
  );
};
Войти в полноэкранный режим

Выйти из полноэкранного режима

Мы продолжим добавлять новые компоненты на эту страницу по мере их создания.


Шаг 3 — Добавить заголовок

Теперь, когда у нас есть контент на странице, мы можем добавить заголовок/панель навигации. Это покажет название нашего продукта, быстрые ссылки для перехода к частям страницы и ссылку на мой Twitter.

Загрузите SVG-изображение значка Twitter отсюда. и поместите его в public папка. Любой файл в этой папке обслуживается как есть. Здесь вы хотите сохранить все статические ресурсы, которые использует ваш сайт.

Давайте начнем с создания нового компонента, который создаст адаптивную панель заголовка. Назови это src/components/Header.tsx :

import { HamburgerIcon } from '@chakra-ui/icons'
import {
  Box,
  chakra,
  Container,
  Drawer,
  DrawerBody,
  DrawerCloseButton,
  DrawerContent,
  DrawerHeader,
  DrawerOverlay,
  Flex,
  Heading,
  IconButton,
  Image,
  Link,
  LinkBox,
  LinkOverlay,
  Spacer,
  Stack,
  useDisclosure,
} from '@chakra-ui/react'

const navLinks = [
  { name: 'Home', link: '/' },
  { name: 'Features', link: '#features' },
  { name: 'Pricing', link: '#pricing' },
]

const DesktopSidebarContents = ({ name }: any) => {
  return (
    <Container maxW={['full', 'container.lg']} p={0}>
      <Stack
        justify="space-between"
        p={[0, 4]}
        w="full"
        direction={['column', 'row']}
      >
        <Box display={{ base: 'none', md: 'flex' }}>
          <Heading fontSize="xl">{name}</Heading>
        </Box>
        <Spacer />
        <Stack
          align="flex-start"
          spacing={[4, 10]}
          direction={['column', 'row']}
        >
          {navLinks.map((navLink: any, i: number) => {
            return (
              <Link
                href={navLink.link}
                key={`navlink_${i}`}
                fontWeight={500}
                variant="ghost"
              >
                {navLink.name}
              </Link>
            )
          })}
        </Stack>
        <Spacer />
        <LinkBox>
          <LinkOverlay href={`https://twitter.com/thisissukh_`} isExternal>
            <Image src="twitter.svg"></Image>
          </LinkOverlay>
        </LinkBox>
      </Stack>
    </Container>
  )
}
const MobileSidebar = ({ name }: any) => {
  const { isOpen, onOpen, onClose } = useDisclosure()

  return (
    <>
      <Flex w="full" align="center">
        <Heading fontSize="xl">{name}</Heading>
        <Spacer />
        <IconButton
          aria-label="Search database"
          icon={<HamburgerIcon />}
          onClick={onOpen}
        />
        <Drawer isOpen={isOpen} placement="right" onClose={onClose} size="xs">
          <DrawerOverlay />
          <DrawerContent bg="gray.50">
            <DrawerCloseButton />
            <DrawerHeader>{name}</DrawerHeader>

            <DrawerBody>
              <DesktopSidebarContents />
            </DrawerBody>
          </DrawerContent>
        </Drawer>
      </Flex>
    </>
  )
}

interface SidebarProps {
  name: string
}

const Sidebar = ({ name }: SidebarProps) => {
  return (
    <chakra.header id="header">
      <Box display={{ base: 'flex', md: 'none' }} p={4}>
        <MobileSidebar name={name} />
      </Box>

      <Box display={{ base: 'none', md: 'flex' }} bg="gray.50">
        <DesktopSidebarContents name={name} />
      </Box>
    </chakra.header>
  )
}

interface HeaderProps {
  name: string
}

export const Header = ({ name }: HeaderProps) => {
  return (
    <Box w="full">
      <Sidebar name={name} />
    </Box>
  )
}
Войти в полноэкранный режим

Выйти из полноэкранного режима

Добавить src/components/Layout.tsx

import { Box, VStack } from "@chakra-ui/react";
import { FunctionComponent } from "react";
import { Header } from "./Header";

interface LayoutProps {
  children: React.ReactNode;
}

export const Layout: FunctionComponent<LayoutProps> = ({
  children,
}: LayoutProps) => {
  return (
    <Box bg="gray.50">
      <VStack spacing={10} w="full" align="center">
        <Header name="Biller" />
        {children}
      </VStack>
    </Box>
  );
};
Войти в полноэкранный режим

Выйти из полноэкранного режима

Обновите src/App.tsx с:

import { Box } from "@chakra-ui/react";
import { HeroSection } from "./components/HeroSection";
import { Layout } from "./components/Layout";

export const App = () => {
  return (
    <Layout>
      <Box bg="gray.50">
        <HeroSection />
      </Box>
    </Layout>
  );
};
Войти в полноэкранный режим

Выйти из полноэкранного режима


Шаг 4 — Добавьте демонстрационное видео

Демонстрационное видео позволяет читателю узнать, чего ожидать при подписке на ваш продукт.

Запишите видео MP4 вашего продукта и поместите его в public папка как video.mp4 . Next.js обслуживает все, начиная с public папка как есть.

Если у вас его нет, вот пример видео и образец постера вы можете использовать.

Добавить демонстрационное видео

Мы также можем добавить poster вариант в video тег, который показывает статическое изображение, если видео еще не загружено. Использование статического кадра видео в качестве постера работает хорошо.

Обновлять src/App.tsx с:

import { Box } from "@chakra-ui/react";
import { HeroSection } from "./components/HeroSection";
import { Layout } from "./components/Layout";

export const App = () => {
  return (
    <Layout>
      <Box bg="gray.50">
        <HeroSection />
        <Container maxW="container.xl">
          <Center p={[0, 10]}>
            <video playsInline autoPlay muted poster="/image.png" loop>
              <source src="/video.mp4" type="video/mp4" />
            </video>
          </Center>
        </Container>
      </Box>
    </Layout>
  );
};
Войти в полноэкранный режим

Выйти из полноэкранного режима


Шаг 5. Добавьте социальное доказательство с логотипами клиентов.

Теперь, когда читатель знает, что это за предложение, вы можете ответить на его следующий логический вопрос — кто еще его использует?

Добавление логотипов клиентов известных компаний, использующих ваш продукт, вызовет доверие у читателя.

Загрузите логотипы SVG для Майкрософт а также Adobe и поместите их в public папка, как и раньше.

Добавить социальное доказательство

Обновите src/App.tsx с:

import {
  Box,
  Center,
  Container,
  Wrap,
  WrapItem,
  Text,
  Image,
} from "@chakra-ui/react";
// ...

export const App = () => {
  return (
    <Layout>
      <Box bg="gray.50">
       // ...

<Container maxW="container.2xl" centerContent py={[20]}>
            <Text color="gray.600" fontSize="lg">
              Used by teams worldwide
            </Text>

            <Wrap
              spacing={[10, 20]}
              mt={8}
              align="center"
              justify="center"
              w="full"
            >
              <WrapItem>
                <Image src="microsoft-logo.svg" alt="Microsoft logo" />
              </WrapItem>

              <WrapItem>
                <Image src="adobe-logo.svg" alt="Adobe logo" />
              </WrapItem>

              <WrapItem>
                <Image src="microsoft-logo.svg" alt="Microsoft logo" />
              </WrapItem>

              <WrapItem>
                <Image src="adobe-logo.svg" alt="Adobe logo" />
              </WrapItem>
            </Wrap>
          </Container>
      </Box>
    </Layout>
  );
};
Войти в полноэкранный режим

Выйти из полноэкранного режима


Шаг 6 — Список функций

В разделе «Функции» вы можете выставить напоказ 3 основных способа, которыми ваш продукт поможет потенциальному пользователю. Я люблю быть максимально прямым.

Функции

Создайте новый файл с именем src/components/Feature.tsx

import {
  Box,
  Button,
  Center,
  Container,
  Stack,
  Text,
  VStack,
  Image,
} from "@chakra-ui/react";
import { FunctionComponent } from "react";

interface FeatureProps {
  title: string;
  description: string;
  image: string;
  reverse?: boolean;
}

export const Feature: FunctionComponent<FeatureProps> = ({
  title,
  description,
  image,
  reverse,
}: FeatureProps) => {
  const rowDirection = reverse ? "row-reverse" : "row";
  return (
    <Center w="full" minH={[null, "90vh"]}>
      <Container maxW="container.xl" rounded="lg">
        <Stack
          spacing={[4, 16]}
          alignItems="center"
          direction={["column", null, rowDirection]}
          w="full"
          h="full"
        >
          <Box rounded="lg">
            <Image
              src={image}
              width={684}
              height={433}
              alt={`Feature: ${title}`}
            />
          </Box>

          <VStack maxW={500} spacing={4} align={["center", "flex-start"]}>
            <Box>
              <Text fontSize="3xl" fontWeight={600} align={["center", "left"]}>
                {title}
              </Text>
            </Box>

            <Text fontSize="md" color="gray.500" textAlign={["center", "left"]}>
              {description}
            </Text>

            <Button
              colorScheme="brand"
              variant="link"
              textAlign={["center", "left"]}
            >
              Learn more 
            </Button>
          </VStack>
        </Stack>
      </Container>
    </Center>
  );
};
Войти в полноэкранный режим

Выйти из полноэкранного режима

Добавить к src/App.tsx

import { Feature } from "./components/Feature";
// ...

interface FeatureType {
  title: string
  description: string
  image: string
}

const features: FeatureType[] = [
  {
    title: "Detailed Analytics",
    description:
      "No more spending hours writing formulas in Excel to figure out how much you're making. We surface important metrics to keep your business going strong.",
    image:
      "https://launchman-space.nyc3.digitaloceanspaces.com/chakra-ui-landing-page-feature-1.png",
  },
  {
    title: "Track your clients",
    description:
      "Know when and how your projects are going so you can stay on top of delivery dates.",
    image:
      "https://launchman-space.nyc3.digitaloceanspaces.com/chakra-ui-landing-page-feature-2.png",
  },
  {
    title: "Manage projects",
    description:
      "You don't have to hunt your email inbox to find that one conversation. Every task, project, and client information is just a click away.",
    image:
      "https://launchman-space.nyc3.digitaloceanspaces.com/chakra-ui-landing-page-feature-3.png",
  },
];

export const App = () => {
  return (
    <Layout>
      <Box bg="gray.50">
        // ...
      <VStack
          backgroundColor="white"
          w="full"
          id="features"
          spacing={16}
          py={[16, 0]}
        >
          {features.map(
            ({ title, description, image }: FeatureType, i: number) => {
              return (
                <Feature
                  key={`feature_${i}`}
                  title={title}
                  description={description}
                  image={image}
                  reverse={i % 2 === 1}
                />
              )
            }
          )}
        </VStack>
      </Box>
    </Layout>
  );
};
Войти в полноэкранный режим

Выйти из полноэкранного режима


Шаг 7 — Добавьте блики

Добавьте новый файл с именем Highlight.tsx

import {
  Box,
  Center,
  Container,
  Wrap,
  WrapItem,
  Text,
  Image,
  VStack,
  SimpleGrid,
} from "@chakra-ui/react";
// ...

export interface HighlightType {
  icon: string
  title: string
  description: string
}

const highlights: HighlightType[] = [
      {
        icon: '',
        title: 'No-code',
        description:
          "We are No-Code friendly. There is no coding required to get started. Launchman connects with Airtable and lets you generate a new page per row. It's just that easy!",
      },
      {
        icon: '🎉',
        title: 'Make Google happy',
        description:
          "We render all our pages server-side; when Google's robots come to index your site, the page does not have to wait for JS to be fetched. This helps you get ranked higher.",
      },
      {
        icon: '😃',
        title: 'Rapid experimenting',
        description:
          "You don't have to wait hours to update your hard-coded landing pages. Figure out what resonates with your customers the most and update the copy in seconds",
      },
      {
        icon: '🔌',
        title: 'Rapid experimenting',
        description:
          "You don't have to wait hours to update your hard-coded landing pages. Figure out what resonates with your customers the most and update the copy in seconds",
      },
    ]

export const App = () => {
  return (
    <Box bg="gray.50">
      // ...
     <Container maxW="container.md" centerContent py={[8, 28]}>
          <SimpleGrid spacingX={10} spacingY={20} minChildWidth="300px">
            {highlights.map(({ title, description, icon }, i: number) => (
              <Box p={4} rounded="md" key={`highlight_${i}`}>
                <Text fontSize="4xl">{icon}</Text>

                <Text fontWeight={500}>{title}</Text>

                <Text color="gray.500" mt={4}>
                  {description}
                </Text>
              </Box>
            ))}
          </SimpleGrid>
        </Container>
    </Box>
  );
};

Войти в полноэкранный режим

Выйти из полноэкранного режима

Особенности


Шаг 8. Добавьте раздел «Цены».

Здесь вы можете добавить на свою страницу раздел с ценами, четко показав, какие функции доступны по какой цене. Предложение годовой подписки может быть выгодным как для вас, так и для клиента.

Цены

Создайте новый компонент в src/components/PricingSection.tsx

import { CheckCircleIcon } from '@chakra-ui/icons'
import {
  Box,
  Button,
  ButtonGroup,
  HStack,
  List,
  ListIcon,
  ListItem,
  SimpleGrid,
  Text,
  VStack,
} from '@chakra-ui/react'
import { FunctionComponent, useState } from 'react'

interface PricingBoxProps {
  pro: boolean
  name: string
  isBilledAnnually: boolean
}

export const PricingBox: FunctionComponent<PricingBoxProps> = ({
  pro,
  name,
  isBilledAnnually,
}: PricingBoxProps) => {
  return (
    <Box
      boxShadow="sm"
      p={6}
      rounded="lg"
      bg={pro ? 'white' : 'white'}
      borderColor={pro ? 'brand.500' : 'gray.200'}
      backgroundColor={pro ? 'brand.50' : 'white'}
      borderWidth={2}
    >
      <VStack spacing={3} align="flex-start">
        <Text fontWeight={600} casing="uppercase" fontSize="sm">
          {name}
        </Text>
        <Box w="full">
          {isBilledAnnually ? (
            <Text fontSize="3xl" fontWeight="medium">
              $89
            </Text>
          ) : (
            <Text fontSize="3xl" fontWeight="medium">
              $99
            </Text>
          )}
          <Text fontSize="sm">per month per site</Text>
        </Box>

        <Text>Unlock key features and higher usage limits</Text>
        <VStack>
          <Button size="sm" colorScheme="brand">
            Free 14-day trial 
          </Button>
        </VStack>

        <VStack pt={8} spacing={4} align="flex-start">
          <Text fontWeight="medium">Everything in Basic, plus:</Text>
          <List spacing={3}>
            <ListItem>
              <HStack align="flex-start" spacing={1}>
                <ListIcon as={CheckCircleIcon} color="brand.500" mt={1} />
                <Text>
                  Lorem ipsum dolor sit amet, consectetur adipisicing elit
                </Text>
              </HStack>
            </ListItem>
          </List>
        </VStack>
      </VStack>
    </Box>
  )
}

interface PricingSectionProps {}

export const PricingSection: FunctionComponent<PricingSectionProps> = () => {
  const [isBilledAnnually, setIsBilledAnnually] = useState<boolean>(true)
  return (
    <VStack spacing={10} align="center">
      <ButtonGroup isAttached>
        <Button
          onClick={() => {
            setIsBilledAnnually(true)
          }}
          colorScheme={isBilledAnnually ? 'brand' : 'gray'}
        >
          Annually (-10%)
        </Button>

        <Button
          onClick={() => {
            setIsBilledAnnually(false)
          }}
          colorScheme={isBilledAnnually ? 'gray' : 'brand'}
        >
          Monthly
        </Button>
      </ButtonGroup>

      <SimpleGrid columns={[1, null, 3]} spacing={10}>
        <PricingBox
          pro={false}
          name="Starter"
          isBilledAnnually={isBilledAnnually}
        />

        <PricingBox
          pro={true}
          name="Creator"
          isBilledAnnually={isBilledAnnually}
        />

        <PricingBox
          pro={false}
          name="Enterprise"
          isBilledAnnually={isBilledAnnually}
        />
      </SimpleGrid>
    </VStack>
  )
}
Войти в полноэкранный режим

Выйти из полноэкранного режима

Добавьте компонент в src/App.tsx

import { PricingSection } from "./components/PricingSection";
// ...

export const App = () => {
  return (
    <Box bg="gray.50">
      // ...
        <Container py={28} maxW="container.lg" w="full" id="pricing">
          <PricingSection />
        </Container>
    </Box>
  );
};

Войти в полноэкранный режим

Выйти из полноэкранного режима


Шаг 9. Добавьте раздел часто задаваемых вопросов

Обработайте возражения вашего потенциального клиента в разделе часто задаваемых вопросов.

Часто задаваемые вопросы

Добавьте компонент под названием src/components/FAQSection.tsx :

import {
  Accordion,
  AccordionButton,
  AccordionIcon,
  AccordionItem,
  AccordionPanel,
  Box,
} from '@chakra-ui/react'

export interface FAQType {
  q: string
  a: string
}

interface FAQSectionProps {
  items: FAQType[]
}

export const FAQSection = ({ items }: FAQSectionProps) => {
  return (
    <Box borderRadius="lg" w="full" p={4}>
      <Accordion>
        {items.map((item: any, i: number) => {
          return (
            <AccordionItem key={`faq_${i}`}>
              <h2>
                <AccordionButton>
                  <Box flex="1" textAlign="left">
                    {item.q}
                  </Box>
                  <AccordionIcon />
                </AccordionButton>
              </h2>

              <AccordionPanel pb={4}>{item.a}</AccordionPanel>
            </AccordionItem>
          )
        })}
      </Accordion>
    </Box>
  )
}
Войти в полноэкранный режим

Выйти из полноэкранного режима

Обновлять src/App.tsx :

import { FAQSection, FAQType } from "./components/FAQSection";
// ...

const faqs: FAQType[] = [
      {
        q: 'How many clients can I bring on?',
        a: 'You can bring on 3 clients with the Free plan. Upgrade to Pro for additional seats.',
      },
      {
        q: 'Can I connect it to my CRM?',
        a: 'Yes! We support Notion and PipeDrive currently.',
      },
      {
        q: 'Do you support international payments?',
        a: 'Yes - payments can be made from and to any country.',
      },
      {
        q: 'Who can I connect to for support?',
        a: 'Email me at sukh@saasbase.dev',
      },
    ]

export const App = () => {
  return (
    <Layout>
      <Box bg="gray.50">
        // ...
        <Container py={28} maxW="container.md">
          <Box w="full">
            <VStack spacing={10} w="full">
              <Text fontWeight={500} fontSize="2xl" align="center">
                Frequently asked questions
              </Text>
              <FAQSection items={faqs} />
            </VStack>
          </Box>
        </Container>
      </Box>
    </Layout>
  );
};
Войти в полноэкранный режим

Выйти из полноэкранного режима


Шаг 10. Добавьте призыв к действию

Добавьте окончательный призыв к действию, чтобы читателю было проще подписаться теперь, когда он знает все о продукте.

призыв к действию

Создайте новый компонент с именем src/components/CTA.tsx :

import { Button, Container, Text, VStack } from '@chakra-ui/react'
import React, { FunctionComponent } from 'react'
import { CTAType } from '../types'

interface CTAProps {
  heading: string
  cta: CTAType
}

export const CTA: FunctionComponent<CTAProps> = ({
  heading,
  cta,
}: CTAProps) => {
  return (
    <Container maxW="container.lg" py={16}>
      <VStack
        spacing={6}
        backgroundColor="brand.500"
        rounded="xl"
        p={6}
        backgroundImage="https://uploads-ssl.webflow.com/60c29c0c0d66236222bfa9b4/60c29c0d0d66230460bfa9e2_Pattern%20Shape.svg"
      >
        <VStack spacing={4} maxW="md">
          <Text fontWeight={600} fontSize="3xl" align="center" color="white">
            {heading}
          </Text>
        </VStack>

        <Button
          size="lg"
          color="brand.500"
          backgroundColor="white"
          onClick={() => {}}
        >
          {cta.name}
        </Button>
      </VStack>
    </Container>
  )
}

Войти в полноэкранный режим

Выйти из полноэкранного режима

Обновлять src/App.tsx :

import { CTA } from '../components/CTA'
// ...

export const App = () => {
  return (
    <Layout>
      <Box bg="gray.50">
        // ...
      <CTA
          heading={`Get started with Biller  today!`}
          cta={{ name: 'I want this!', link: '#' }}
        />
      </Box>
    </Layout>
  );
};
Войти в полноэкранный режим

Выйти из полноэкранного режима


Шаг 11 — Добавьте нижний колонтитул

Добавьте нижний колонтитул с социальной ссылкой на Twitter.

Обновлять src/App.tsx :

import {
  Container,
  Box,
  Center,
  Text,
  Wrap,
  WrapItem,
  Image,
  VStack,
  SimpleGrid,
  Flex,
  LinkBox,
  LinkOverlay,
  Spacer,
} from "@chakra-ui/react";
// ...

export const App = () => {
  return (
    <Layout>
      <Box bg="gray.50">
       // ...
        <Container maxW="container.lg">
          <Flex py={6}>
            <Box>
              <Text>© 2022 Biller</Text>

              <Text>Made by Sukh</Text>
            </Box>
            <Spacer />

            <LinkBox>
              <LinkOverlay href="https://twitter.com/@thisissukh_" isExternal>
                <Image src="twitter.svg" alt="Twitter logo"></Image>
              </LinkOverlay>
            </LinkBox>
          </Flex>
        </Container>
      </Box>
    </Layout>
  );
};
Войти в полноэкранный режим

Выйти из полноэкранного режима

Нижний колонтитул


Шаг 12 — Добавьте SEO

React Helmet — отличный пакет, который помогает нам оптимизировать SEO на странице. Установите его:

npm i react-helmet
npm i --save-dev @types/react-helmet
Войти в полноэкранный режим

Выйти из полноэкранного режима

Обновлять src/App.tsx :

import {Helmet} from "react-helmet";

export const App = () => {
  return (
    <Layout>
      <Helmet>
        <meta charSet="utf-8" />
        <title>Biller | Get paid faster</title>
      </Helmet>
      <Box bg="gray.50">
         // ...
      </Box>
    </Layout>
  );
};
Войти в полноэкранный режим

Выйти из полноэкранного режима


Шаг 13. Развертывание на Vercel

Vercel — самая плавная платформа для развертывания, которую я когда-либо использовал.

Разверните свой проект React/Vite:

  1. Загрузить исходный код на Github
  2. Выбирать Vite как предустановка фреймворка

Версель Развертывание

3. Нажмите «Развернуть».

Вот и все — у вас есть новая целевая страница, готовая к работе с пользовательским интерфейсом Chakra!