現代オープンソースポートフォリオの開発👨🏾‍💻


かなりの時間の間、私は私のポートフォリオウェブサイトに取り組んでいましたwww.josemukorivo.com . 私が私がインターンシップをしていたとき、私がこれの前に使っていたポートフォリオは、2018年に戻って開発されました.最近、私はそれが時代遅れであると感じました、実際、サイトが発射の後、決して更新されませんでした.

旧ウェブサイトヒーローセクション



概要


私はポートフォリオの開発者遊び場として考えることが好きですので、時々、10のレプリカとKubernetes😃 単に開発者がいくつかのクールなKubernetes機能をテストしたいので.ここでの私のポイントは、私が選んだスタックがポートフォリオのためのオーバーキルであるかもしれないということです😎?

デザイン


私がポートフォリオを開発し始める前に、私は私が私のポートフォリオが欲しかったもののwiflamesをつくることによって始めました.私はペンと小さな本を使ってこれらの無線を作成しました.以下はポートフォリオのサンプルワイヤーフレームです.

ワイレッドズを設計した後figma 私が開発に使用した実際のデザインのために.ポートフォリオ上のすべては、開発中にいくつかのものが追加されたFigmaで設計されていないことに注意してください、私はデザインを変更したので、コード化されていないfigmaに設計されたいくつかのものもあります.以下は、デザインの最初の反復がどう図のように見えたかです.

内容


デザインの後の次のものは、ウェブサイトのために内容を生成していました.私にとって、それは難しいステージのうちの1つでした、しかし、幸いに、私はあまりに多くの内容が欲しくありませんでした.信じられるGitHub Copilot いくつかの内容で私を助けてくれた🤔, 私は、AIがその利益であることを意味します👌🏼.

開発


コンテンツのデザインと生成後、ポートフォリオの開発を開始しました.私が詳細に入る前に、あなたに私にポートフォリオを供給しているもののいくつかのリストを与えさせました.

  • React UI用

  • NextJS SSG/SSR/ISSG

  • Tailwind css スタイリング
  • クラス名衝突なしでCSSモジュールスタイル

  • Mailchimp
  • Vercel
  • GitHub
  • Framer motion
  • TypeScript
  • 開発は最も興味深いステージだったので、私はおそらくここでもう少し多くの時間を過ごすつもりです.このポートフォリオのコアではReactJS . 私はすべてのコンポーネントを作成するために反応を使用します.下記は再利用可能なサンプルですButton コンポーネントとそのスタイル.

    ボタン.TSX
    import {
      FC,
      forwardRef,
      useRef,
      ButtonHTMLAttributes,
      JSXElementConstructor,
    } from 'react';
    import Link from 'next/link';
    import cn from 'classnames';
    
    import s from './Button.module.scss';
    import { mergeRefs } from '@utils/index';
    
    interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
      href?: string;
      className?: string;
      disabled?: boolean;
      loading?: boolean;
      target?: '_blank' | '_self' | '_parent' | '_top';
      size?: 'sm' | 'md' | 'lg';
      type?: 'button' | 'submit' | 'reset';
      variant?: 'primary' | 'secondary' | 'naked';
      as?: 'button' | 'a' | JSXElementConstructor<any>;
    }
    
    export const Button: FC<ButtonProps> = forwardRef((props, buttonRef) => {
      const {
        as: Tag = 'button',
        variant = 'naked',
        size = 'md',
        type = 'button',
        target = '_self',
        href,
        className,
        disabled,
        children,
        ...rest
      } = props;
      const ref = useRef<typeof Tag>(null);
      const classes = cn(
        s.root,
        {
          [s.primary]: variant === 'primary',
          [s.secondary]: variant === 'secondary',
          [s.naked]: variant === 'naked',
          [s.sm]: size === 'sm',
          [s.md]: size === 'md',
          [s.lg]: size === 'lg',
          [s.disabled]: disabled,
        },
        className
      );
    
      return (
        <>
          {href ? (
            <Tag ref={mergeRefs([ref, buttonRef])} {...rest}>
              <Link href={href}>
                <a className={classes} target={target}>
                  {children}
                </a>
              </Link>
            </Tag>
          ) : (
            <Tag
              ref={mergeRefs([ref, buttonRef])}
              disabled={disabled}
              className={classes}
              {...rest}
            >
              {children}
            </Tag>
          )}
        </>
      );
    });
    
    Button.displayName = 'Button';
    
    

    ボタン.モジュールです.SCSS
    .root {
      @apply mb-1 inline-block transition duration-200 ease-linear;
    }
    
    .primary {
      @apply bg-rose-500 text-white ring-rose-500 ring-offset-2 hover:bg-rose-600 hover:ring dark:ring-offset-slate-900 2xl:ring-offset-4;
    }
    
    .secondary {
      @apply bg-slate-800 text-white ring-slate-800 ring-offset-2 hover:bg-slate-900 hover:ring dark:bg-slate-700 dark:ring-slate-700 dark:ring-offset-slate-900 2xl:ring-offset-4;
    }
    
    .naked {
      @apply bg-white text-slate-900;
    }
    
    .md {
      @apply px-6 py-2;
    }
    
    .lg {
      @apply px-7 py-4 text-sm md:px-8;
    }
    
    .disabled {
      @apply cursor-not-allowed opacity-30;
    }
    
    
    コンポーネントは、タイプセーフのタイプスクリプトを使用してコードです.TypeScriptはまた、自己文書化されているコードを書くことに役立ちます.スタイリング用tailwindcss しかし、対応するコンポーネントのクラスがクリーンであることに注意してください.CSS module . CSSモジュールは、CSSクラスの名前空間衝突を避けるのに役立ちます.以下はどのようになりますButton を使用できます.
    <Button
       variant='primary'
       size='lg'
       href='/blog'
       className='uppercase'
      >
       Read my blog
    </Button>
    
    私の再利用可能なコンポーネントのほとんどすべてがこのように符号化されます.
    これらの小さなコンポーネントButton , Text , Link and Boxcomponents/ui/ フォルダを使用してエクスポートindex.ts そうすることができるようにファイルを
    import { Text, Box, Container, Link, Button } from '@components/ui';
    
    ヒーローのセクションのようなセクションはcomponents/sections/ フォルダとそのような共通の要素Navigation and Footer 中にいるcomponents/common/ フォルダ.

    ダークモード


    このポートフォリオはライトモードとダークモードの両方を持っています.これはテールウインドのダークモードサポートによって簡単になりました.サイトを最初に訪れたとき、あなたのシステムの設定でダークモードが有効になっているかどうかを確認します.暗闇と光のモードを切り替えることができますボタンもあります.私もテーマを切り替えるためのカスタムフックを作成し、私はそれのコードを表示させてください.

    useTheme hook
    import { useEffect, useState } from 'react';
    
    export const useTheme = () => {
      const getTheme = (): 'light' | 'dark' => {
        // Check user preference for theme first
        if (
          window.localStorage.theme === 'dark' ||
          (!('theme' in window.localStorage) &&
            window.matchMedia('(prefers-color-scheme: dark)').matches)
        ) {
          return 'dark';
        } else {
          return 'light';
        }
      };
    
      const [theme, setTheme] = useState('');
    
      const toggleTheme = () => {
        if (theme === 'light') {
          setTheme('dark');
          window.localStorage.setItem('theme', 'dark');
        } else {
          setTheme('light');
          window.localStorage.setItem('theme', 'light');
        }
      };
    
      useEffect(() => {
        setTheme(getTheme());
        if (theme === 'dark') {
          document.documentElement.classList.add('dark');
        } else {
          document.documentElement.classList.remove('dark');
        }
      }, [theme]);
    
      return { theme, toggleTheme };
    };
    
    

    使用例
    import { BiSun, BiMoon } from 'react-icons/bi';
    import { useTheme } from 'hooks';
    
    export const Example = () => {
    
    const { theme, toggleTheme } = useTheme();
    
      return (
        <Box>
         <Button onClick={toggleTheme}>
           {theme === 'dark' ? (
              <BiSun className='h-4 w-auto' />
            ) : (
              <BiMoon className='h-4 w-auto' />
            )}
            {theme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode'}
         </Button>
        </Box>
      );
    };
    
    

    SEO


    これは単純なポートフォリオのウェブサイトだったにもかかわらず、私はそれが検索エンジンフレンドリーであることを確認しなければならなかった.私はPage いくつかのSEOデータをpropsとして、サイト上のすべてのページを取得するコンポーネントは、親として使用します.

    ページ.TSX
    import { FC } from 'react';
    import Head from 'next/head';
    
    interface Props {
      title: string;
      description: string;
      image: string;
      canonicalURL?: string;
    }
    
    export const Page: FC<Props> = ({
      children,
      title,
      description,
      image,
      canonicalURL,
    }) => {
      return (
        <>
          <Head>
            <title>{title}</title>
            <meta name='description' content={description} />
            <meta
              name='keywords'
              content='Joseph, Mukorivo, Joseph Mukorivo, software engineer, Harare, Zimbabwe, Harare software developer, zimbabwe developer blog, software development blog, DevOps blog, Cloud Computing blog, React Developer, React Blog'
            />
            <meta name='author' content='Joseph Mukorivo' />
            <meta name='image' content={image} />
            <meta name='og:title' content={title} />
            <meta name='og:description' content={description} />
            <meta name='og:image' content={image} />
            <meta name='og:url' content='https://josemukorivo.com' />
            <meta name='og:site_name' content='Joseph Mukorivo' />
            <meta name='og:type' content='website' />
            <meta name='twitter:card' content='summary_large_image' />
            <meta name='twitter:title' content={title} />
            <meta name='twitter:alt' content={title} />
            <meta name='twitter:description' content={description} />
            <meta name='twitter:image' content={image} />
            <meta name='theme-color' content='#f43f5e' />
            <meta name='twitter:site' content='@josemukorivo' />
            <meta name='twitter:creator' content='@josemukorivo' />
            {canonicalURL && <link rel='canonical' href={canonicalURL} />}
          </Head>
          <main>{children}</main>
        </>
      );
    };
    
    

    例ページ用法
    import { About, Hero, LatestBlogs, TechStack } from '@components/sections';
    import { Footer, Nav, Page } from '@components/common';
    
    export default function Home({ articles }) {
      return (
        <Page
          title='Joseph Mukorivo | Software Engineer'
          description='Joseph Mukorivo is a Software Engineer, Blogger and DevOps Enthusiat based in Harare, Zimbabwe.'
          image='https://josemukorivo.com/images/me.jpeg'
        >
          <Nav className='absolute py-3 md:py-5' />
          <Hero />
          <About />
          <LatestBlogs articles={articles} />
          <TechStack />
          <Footer />
        </Page>
      );
    }
    
    Google Analytics、Google MyビジネスとSEOのためのGoogle検索コンソールのような他のツールも使用しました.今私の名前の簡単なGoogle検索Joseph Mukorivo ユーザーに私に関する詳細情報を与えます.
    私はまた、ソーシャルメディア上で共有するときにサイトがいくつかの良い意見を持っていることを確認するためにいくつかのオープングラフタグを使用しました.

    ブログ


    私のブログはストレートから来ています、そして、私はNextjsを使っているので、そのようにビルド時間でブログ内容でページを構築します.nextjsの詳細は見つかりますhere
    export async function getStaticProps() {
      const res = await fetch(
       `https://dev.to/api/articles?username=${DEV_TO_USERNAME}&per_page=5`
      );
      const articles = await res.json();
    
      return {
        props: {
          articles,
        },
        revalidate: 60,
      };
    }
    
    

    クール機能


    私は、ウェブサイトをカスタマイズするためのウィジェットを追加しました.それは、ユーザーが光と暗いモードを切り替えるために使用するものですが、彼らはまた、色相フィルタを適用するか、右から左に読む人々のために有用であるかもしれないテキストの配置を変更するには、ウェブサイトを黒と白に変えるためにそれを使用することができます.他の機能を働かせると、このウィジェットはまだ開発中です.このウィジェットのアイデアは私のものから来ました.

    メーリングリスト


    私はまた、私はWeb開発といくつかのクラウド技術についていくつかのヒントを共有するメーリングリストを運営している.購読すれば嬉しいです😎. メーリングリストはmailchimpを使用して実行されます.

    ホスティング


    このポートフォリオはVercel そして、私がGithubの上で主要な支店に押すたびに、それは再建します.

    ファイナル・モード・ヒーロー



    倉庫


    私は他の人々がそれから学ぶことができて、また、改善するのを助けるように、私のポートフォリオをオープンソースしました.あなたは自由にフォークし、レポと常に改善と他のクールな機能とプル要求を提出するために歓迎スター😎.
    ここへのリンクはrepository

    どうぞ


    コードをコピーしないでください、そして、変更なしでそれをホストしてください.あなたが私のもののように正確に見えないようにデザインと他のものを変えるようにしてくださいwww.josemukorivo.com 常に有り難いです.
    私はこの記事が何かを助けてくれることを願っています.あなたは私に従うか、私の購読することができますmailing list 私が働いていることに関する若干の情報を得るために.