Built a portfolio page with Gatsby
/ GraphQL
, using styled components
. On my local machine, it looks and functions exactly as I want it to. When deployed, there seem to be button style issues and one of my images isn't rendering at all. I tried deploying on both Netlify
and Surge.sh
, but had the same result both times. What am I doing incorrectly? Edit: could my button styles be affected by how Link
is used?
Here are how my button styles look locally:
Here's how they look deployed ('View Repo' doesn't even work deployed):
My 'About Me' section locally (2 columns):
My 'About Me' section deployed (only column 1 shows):
gatsby-config.js
module.exports = {
siteMetadata: {
title: `Portfolio`,
description: `Lorem ipsum.`,
author: `@jordanwhunter`,
},
plugins: [
`gatsby-plugin-react-helmet`,
`gatsby-plugin-image`,
`gatsby-plugin-styled-components`,
{
resolve: `gatsby-source-filesystem`,
options: {
name: `images`,
path: `${__dirname}/src/assets/images`,
},
},
{
resolve: `gatsby-source-filesystem`,
options: {
name: `videos`,
path: `${__dirname}/src/assets/videos`,
},
},
`gatsby-transformer-sharp`,
`gatsby-plugin-sharp`,
{
resolve: `gatsby-plugin-manifest`,
options: {
name: `gatsby-starter-default`,
short_name: `starter`,
start_url: `/`,
background_color: `#663399`,
theme_color: `#663399`,
display: `minimal-ui`,
icon: `src/assets/images/memoji.jpeg`, // This path is relative to the root of the site.
},
},
`gatsby-plugin-gatsby-cloud`,
`gatsby-transformer-json`,
{
resolve: `gatsby-source-filesystem`,
options: {
path: `./src/data/`,
},
},
],
}
package.json
{
"name": "gatsby-starter-default",
"private": true,
"description": "A simple starter to get up and developing quickly with Gatsby",
"version": "0.1.0",
"author": "Kyle Mathews <[email protected]>",
"dependencies": {
"babel-plugin-styled-components": "^1.12.0",
"gatsby": "^3.0.1",
"gatsby-image": "^3.0.0",
"gatsby-plugin-gatsby-cloud": "^2.0.0",
"gatsby-plugin-image": "^1.0.0",
"gatsby-plugin-manifest": "^3.0.0",
"gatsby-plugin-offline": "^4.0.0",
"gatsby-plugin-react-helmet": "^4.0.0",
"gatsby-plugin-sharp": "^3.0.0",
"gatsby-plugin-styled-components": "^4.0.0",
"gatsby-source-filesystem": "^3.0.0",
"gatsby-transformer-json": "^3.0.0",
"gatsby-transformer-sharp": "^3.0.0",
"gh-pages": "^3.1.0",
"prop-types": "^15.7.2",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-helmet": "^6.1.0",
"react-icons": "^4.2.0",
"styled-components": "^5.2.1",
"surge": "^0.22.1"
},
"devDependencies": {
"prettier": "2.2.1"
},
"keywords": [
"gatsby"
],
"license": "0BSD",
"scripts": {
"build": "gatsby build",
"develop": "gatsby develop",
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,md}\"",
"start": "npm run develop",
"serve": "gatsby serve",
"clean": "gatsby clean",
"test": "echo \"Write tests! -> https://gatsby.dev/unit-testing\" && exit 1"
},
"repository": {
"type": "git",
"url": "https://github.com/gatsbyjs/gatsby-starter-default"
},
"bugs": {
"url": "https://github.com/gatsbyjs/gatsby/issues"
}
}
Portfolio.js (button code starts on line 226)
import React from 'react'
import { useStaticQuery, graphql } from "gatsby"
import { Button } from "./Button"
import Img from "gatsby-image"
import styled from "styled-components"
const PortfolioContainer = styled.div`
min-height: 100vh;
padding: 5rem calc((100vw - 1300px) / 2);
background: #fff;
color: #fff;
`
const PortfolioHeading = styled.div`
font-size: clamp(1.5rem, 5vw, 2.5rem);
text-align: center;
margin-bottom: 5rem;
color: #000;
`
const PortfolioImg = styled(Img)`
height: 100%;
max-width: 100%;
position: absolute;
border-radius: 10px;
filter: brightness(100%);
transition: 0.4s cubic-bezier(0.075, 0.82, 0.165, 1);
`
const PortfolioWrapper = styled.div`
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-gap: 10px;
justify-items: center;
padding: 0 2rem;
@media screen and (max-width: 1200px) {
grid-template-columns: 1fr 1fr;
}
@media screen and (max-width: 868px) {
grid-template-columns: 1fr;
}
`
const PortfolioInfo = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-content: center;
align-items: center;
justify-items: center;
padding: 0 2rem;
visibility: hidden;
opacity: 0;
transition: opacity .2s, visibility .2s;
@media screen and (max-width: 280px) {
padding: 0 1rem;
justify-content: center;
align-content: center;
}
`
const PortfolioCard = styled.div`
line-height: 2;
width: 100%;
height: 500px;
position: relative;
border-radius: 10px;
transition: 0.2s ease;
&:hover ${PortfolioInfo}{
visibility: visible;
opacity: 1;
}
&:hover ${PortfolioImg}{
filter: brightness(50%)
}
`
const TextWrap = styled.div`
display: flex;
align-items: center;
text-align: center;
position: absolute;
top: 375px;
flex-wrap: wrap;
justify-content: center;
align-content: center;
width: 87%;
`
const PortfolioTitle = styled.div`
font-weight: 400;
font-size: 1rem;
margin-left: 0.5rem;
@media screen and (max-width: 280px) {
font-size: 12px;
}
`
const PortfolioDescription = styled.div`
font-size: 1rem;
@media screen and (max-width: 280px) {
font-size: 12px;
}
`
const PortfolioTechnologies = styled.div`
font-size: 1rem;
@media screen and (max-width: 280px) {
font-size: 12px;
}
`
const ButtonLink = styled.a`
text-decoration: none;
cursor: pointer;
`
const ButtonWrap = styled.div`
display: flex;
flex-direction: row;
position: absolute;
justify-content: center;
align-content: center;
align-items: center;
width: 100%;
height: -400px;
z-index: 1;
gap: 10px;
@media screen and (max-width: 280px) {
padding: 0 1rem;
justify-content: center;
align-content: center;
}
`
const CustomButton = styled(Button)`
display: flex;
align-items: center;
position: relative;
font-size: 14px;
width: 100%;
cursor: pointer;
top: -60px;
@media screen and (max-width: 480px) {
background: none;
border: none;
padding: 0 !important;
font-family: arial, sans-serif;
color: #fff;
text-decoration: underline;
cursor: pointer;
width: 100%;
font-size: 12px;
justify-content: center;
}
`
const CustomP = styled.p`
font-size: 12px;
`
export default function Portfolio({ heading }) {
const data = useStaticQuery(graphql`
query PortfolioQuery {
allPortfolioJson {
edges {
node {
alt
button1
button2
description
name
technologies
demo
repo
img {
childImageSharp {
fluid {
...GatsbyImageSharpFluid
}
}
}
}
}
}
}
`)
function getPortfolio(data) {
const portfolioArray = []
data.allPortfolioJson.edges.forEach((item, index) => {
portfolioArray.push(
<PortfolioCard key={index}>
<PortfolioImg
src={item.node.img.childImageSharp.fluid.src}
fluid={item.node.img.childImageSharp.fluid}
alt={item.node.alt}
/>
<PortfolioInfo>
<TextWrap>
<PortfolioTitle
css={`
margin-top: -500px;
`}
>
<strong><u>Project:</u></strong> <br />
{item.node.name}
</PortfolioTitle>
<PortfolioDescription
css={`
margin-top: -300px;
`}
>
<strong><u>Description:</u></strong> <br />
{item.node.description}
</PortfolioDescription>
<PortfolioTechnologies
css={`
margin-top: -100px;
`}
>
<strong><u>Technologies:</u></strong> <br />
{item.node.technologies}
</PortfolioTechnologies>
</TextWrap>
<ButtonWrap>
<ButtonLink
href={`${item.node.demo}`}
target="_blank"
>
<CustomButton
primary="true"
round="true"
>
{item.node.button1}
</CustomButton>
</ButtonLink>
<ButtonLink
href={`${item.node.repo}`}
target="_blank"
>
<CustomButton
primary="true"
round="true"
>
{item.node.button2}
</CustomButton>
</ButtonLink>
</ButtonWrap>
</PortfolioInfo>
</PortfolioCard>
)
})
return portfolioArray;
}
return (
<PortfolioContainer id="portfolio">
<PortfolioHeading>
{heading}
<CustomP>(Tap on Mobile)</CustomP>
</PortfolioHeading>
<PortfolioWrapper>
{getPortfolio(data)}
</PortfolioWrapper>
</PortfolioContainer>
)
};
About.js (column 2 code starts on line 137):
import React from 'react'
import { useStaticQuery, graphql } from 'gatsby';
import { GrCircleInformation, GrCode, GrDocumentImage } from "react-icons/gr";
import styled from "styled-components"
import Img from "gatsby-image"
const AboutContainer = styled.div`
width: 100%;
background: #fcfcfc;
color: #000;
padding: 5rem calc((100vw - 1300px) / 2);
height: 100%;
border-top: 1px solid gray;
border-bottom: 1px solid gray;
`
const TopLine = styled.div`
color: #077bf1;
font-size: 1rem;
padding-left: 2rem;
margin-bottom: 0.75rem;
`
const Description = styled.p`
text-align: start;
padding-left: 2rem;
margin-bottom: 4rem;
font-size: clamp(1.5rem, 5vw, 2rem);
font-weight: bold;
`
const ContentWrapper = styled.div`
display: grid;
grid-template-columns: 1fr 1fr;
padding: 0 2rem;
@media screen and (max-width: 768px) {
grid-template-columns: 1fr;
}
`
const ColumnOne = styled.div`
display: grid;
grid-template-rows: 1fr 1fr;
`
const Biography = styled.div`
padding-top: 1rem;
padding-right: 2rem;
h3 {
margin-bottom: 1rem;
font-size: 1.5rem;
font-style: italic;
}
p {
color: #3b3b3b;
}
`
const ColumnTwo = styled.div`
display: grid;
grid-template-columns: 1fr;
margin-top: 2rem;
grid-gap: 10px;
@media screen and (max-width: 500px) {
grid-template-columns: 1fr;
}
`
const Image = styled(Img)`
border-radius: 10px;
height: 100%;
margin-top: -50px;
@media screen and (max-width: 375px) {
margin-top: 0;
}
`
export default function About() {
const data = useStaticQuery(graphql`
query MyQuery {
allFile(filter: {ext: {regex: "/(jpg)|(png)|(jpeg)/"}, name: {in: ["profile-photo"]}}) {
edges {
node {
childImageSharp {
fluid {
...GatsbyImageSharpFluid
}
}
}
}
}
}
`)
return (
<AboutContainer id="about">
<TopLine>
About Me
</TopLine>
<Description>
<div css={`display: flex;`}>
<GrDocumentImage /><h4 css={`font-style: italic;`}>Resumes</h4>
</div>
<div
css={`
display: flex;
font-size: 14px;
margin-bottom: -40px;
`}
>
<p>
<a href="https://docs.google.com/document/d/162ZfqqYxwYgeaY4fk7pf3GdtVn_Kp-ONqWOVCWzIhqo/edit?usp=sharing" target="_blank">Resume 2021 (ATS Version)</a><br />
<a href="https://docs.google.com/document/d/1u87owpbLG2uoPAqvoVmkuh_LEGSJuRa69OVAYm3hELU/edit?usp=sharing" target="_blank">Resume 2021 (Styled Version)</a>
</p>
</div>
</Description>
<ContentWrapper>
<ColumnOne>
<Biography>
<div css={`display: flex;`}>
<GrCircleInformation /><h3>Brand Statement</h3>
</div>
<p>
Full stack web/software developer with an entrepreneurial spirit, and keen sense of efficiency and time management. A passionate, goal-oriented team player that strives to always write clean, precise code focused on mobile responsive themes. Maintains a problem solving, can-do attitude and exhibits consistent eagerness to learn new technologies/techniques.
</p>
</Biography>
<Biography>
<div css={`display: flex;`}>
<GrCode />
<h3>Technologies</h3>
</div>
<p>
JavaScript, React, Preact, Next, Gatsby, Svelte, Node, Express, Firebase, Vercel, MongoDB, MySQL, Handlebars, jQuery, D3, GraphQL, Material-UI, CSS3, Bootstrap, Materialize, Bulma, HTML5
</p>
</Biography>
</ColumnOne>
<ColumnTwo>
{data.allFile.edges.map((image, key) => (
<Image
key={key}
src={image.node.childImageSharp.src}
fluid={image.node.childImageSharp.fluid}
/>
))}
</ColumnTwo>
</ContentWrapper>
</AboutContainer>
)
};
Inspect generated html:
<div class="Portfolio__ButtonWrap-zwd7jb-14 boxtca">
<a href="https://filmapi.vercel.app/" target="_blank" class="Portfolio__ButtonLink-zwd7jb-13 dHdqal">
<a primary="true" round="true" class="Button-sc-1t76fnu-0 Portfolio__CustomButton-zwd7jb-15 iA-DhcJ cycuPQ">View App</a>
</a>
<a primary="true" round="true" class="Button-sc-1t76fnu-0 Portfolio__CustomButton-zwd7jb-15 iA-DhcJ cycuPQ">
<a primary="true" round="true" class="Button-sc-1t76fnu-0 Portfolio__CustomButton-zwd7jb-15 iA-DhcJ cycuPQ">View Repo</a>
</a>
</div>
ButtonLink
);key
prop (the most frequent bad React rendering reason?);key
prop for [elements of] lists/arrays/groups!React update (rerendering) breaks child divs [of AboutContainer
] - no key
s used (then no unique identifiers for multiple divs in VDOM), result - the update breaks the content structure.
But this is not the only reason - the real one was using <Img/>
-based component - outdated for new gatsby image plugins [versions].
<GatsbyImage/>
components.