The official University of Maryland, College Park Taiwanese American Student Association website. It utilizes Netlify CMS to simplfy adding new events and editing bios. Plus, it comes with helpful link shortening!
- Installation
- Files to Change
- Customizing Theme and Styling
- Mailchimp Integration
- Netlify CMS
- Adding New Pages
- Adding New Events
- Adding New Archived Board Page
- Editing Bios
- Link Shortening
- GraphQL Type Generation
- Miscellaneous
Everything is already set-up in the package.json so all you have to do is
yarn install
Follow the yarn installation docs if you need to install yarn!
You will have to install gatsby-cli which you can do with npm install -g gatsby-cli
Their website has a nice tutorial which I recommend following.
You also can find the original Gatsby's original README.md here. That doc details a quick look at some file structure and basic files for this repo
site-config.js
- Contains config information about our site like site Title, description, URL, preferred date formats, links
gatsby-config.js
- The main config file for Gatsby. This file takes some information from our
site-config.js
as well as the various plugins we've installed. - Add our Mailchimp endpoint here! See Mailchimp Integration
- The main config file for Gatsby. This file takes some information from our
config.yml
- The config file that determines how our Netlify CMS looks and what we can add
- In particular, add any new tags you'd like to add to events. See Netlify CMS
theme.tsx
- The theme used throughout our site. Add primary and secondary colors and any new fonts. Be sure to add fonts to the
gatsby-plugin-prefetch-google-fonts
too.
- The theme used throughout our site. Add primary and secondary colors and any new fonts. Be sure to add fonts to the
Header.tsx
- When you add pages, be sure to add the links to them here!
- Everything else is individual components on the site!
This site uses Material-UI components for styling. The theme can be modified in theme.tsx
to change the primary and secondary colors, the spacing used throughout the site, and typography. More info about customizing theme can be found on the official Material-UI cutomization guide. The theme is provided to all the pages in gatsby-browser.js
via ThemeProvider.
The Material-UI framework relies on the idea of css-in-js. In particular, we use Material-UI's withStyles method of adding styles.
We can add styles to components by defining CSS in the styles
object. There's a slight difference in naming between usual CSS fields and CSS-in-JS fields, but that's usually replacing - with camelCase. We can also use media queries to use different styles depending on the size of the screen! Examples can be seen in the Header.tsx
component where it sets the fontsize to be "18px" if the screen is smaller than the "sm" size defined in the theme.
[theme.breakpoints.down("sm")]: {
fontSize: "18px",
},
The styles object takes in the optional theme parameter to use information specified by our theme. We use the createStyles
function so that TypeScript can properly recognize the type of the styles object. We then use WithStyles<typeof styles>
to get the type of the styles object. The withStyles function passes down classes
as a prop to the component (this is known as higher order composition), and the component can then use the classes
object to extract out the classNames.
All h1, h2, h3, etc elements can be customized across the site in themes as well. Here's the example from the Material-UI documentation
const theme = createMuiTheme({
typography: {
subtitle1: {
fontSize: 12,
},
body1: {
fontWeight: 500,
},
button: {
fontStyle: "italic",
},
},
})
In order to add a white color option, I created a Text.tsx
component that wraps the Typography
component from Material-UI. Also, since it has a shorter name, it's more likely to fit on one line.
We use Typography so that we can get the benefits of responsive text sizing and unified styling. As noted above, we can customize how all heading elemnts look, and we have to use Typography components to benefit from that styling.
- Add the Mailchimp endpoint to
gatsby-config.js
by following the instructions listed on gatsby-plugin-mailchimp - Modify and stylize the
Newsletter.tsx
component to look how you want it to - Use the
Newsletter.tsx
component where you want to display it
Netlify CMS (content management system) is a way to easily add new events, bios, and whatever else we choose to add. The interface uses config.yml
located at /static/admin/config.yml
which defines where data should be stored, what folders they should be put in, and what data can be added. The collections field allows us to specify the type of data we can add to the site.
Our site has events and bios. When the user signs into the Netlify CMS admin panel (see below
), they see the types of data they can add. This can include titles, images, tags, and markdown content. Then, Gatsby takes this information and displays it on our site.
Be sure to modify config.yml
so that the repo on line 4 corresponds to your own repo! In addition, be sure to create an OAuth application by following the official Netlify guide and add it to your Netlify site via the dashboard.
To add new pages, just create a file in src/pages/
. Gatsby will turn that file into a page on the site with the same name. Layout and ThemeProvider are automatically added by gatsby-browser
. The Layout component wraps our page in a header and footer. You find the components used by the Layout in src/Layout
Also, since that new component is a page component, it can take PageProps
from Gatsby which includes location information. More information about pages and layouts can be found in the Gatsby page documentation.
Through Netlify CMS, we have a nice interface to add events. You can access it by following the url of the site [url]/admin
Then, sign in with an account that has access to the repository. Once you're in, you should see
You can click on an existing event to edit it or on "New Events" to create a new event. From there, you should see Once all the fields are filled out, hit publish and a markdown file and image file should be added to the GitHub repository! Then, Netlify will rebuild the site with the new content.
When new events are created via the Netlify CMS interface, a markdown file gets added to content/events
and the uploaded image gets added to static/assets/
. Then, gatsby-node.js
creates a new page using EventPageTemplate.tsx
.
The markdown file has a section called frontmatter and the actual body which should be the description. We can then query graphql to get this file. For the event pages, this happens at the page level, so we can query for the file and the corresponding image.
For everywhere else, we have to use static queries which can't take parameters. We get the events through the hook useEvents.tsx
. More about hooks can be found on the official React site. The useEvents hook queries for all of the event markdown files and every image. Then, it filters based off any tags given and returns a combined node with the associated image.
We can use the result from useEvents to display the data in various ways such as in a grid. If we wanted to get upcoming or previous events, we can add a filter to the useEvents hook which can return us events that occur before or after a certain date.
The Event.tsx
component displays a card with information about the event. If you click on the card, it brings us to the dedicated page for that event.
This is not the best way to go about it. This utilizes static queries, but a better way might be to use page queries (like it is done for events). This would automatically create the pages, assuming the data is correctly inputted. Whoever is reading this, should try and do it!!!
Edit static/admin/config.yml file to include the desired archived board20## collection. See below for example:
- name: board2023
identifier_field: name
label: "Board2023"
folder: "content/board2023"
create: true
sortableFields: ["name", "position"]
fields:
- { label: "Name", name: "name", widget: "string" }
- {
label: "Category",
name: "category",
widget: "hidden",
default: "board2023",
}
- {
label: "Profile Picture",
name: "imgsrc",
widget: "image",
hint: "Square images are preferred!",
}
Make sure to change the text for the following fields (name, label, folder, fields[default])
Edit gatsby-config.js and add a new plugin sharing the same name as the collection in step 1. Follow the same structure in this example:
...},
{
resolve: "gatsby-source-filesystem",
options: {
name: "board2023",
path: `${__dirname}/content/board2023/`,
},
},
{...
Make sure to change the text for the following fields (name, path).
Add a board image (for the page's background) in src/images/ called 'board20##'. To improve load times, please change the image size to less than 1MB (change resolution; I found 1280x1280 will work well).
Add the board photos in static/assets/Board20##. To improve load times, please change the photos' size to less than 1MB (change resolution; I found 640x640 will work well).
Create a folder under content/ with the same name as the collection. This is where you will fill with .md files (bios). Refer to other .md files (bios) for reference.
Alternatively, you can push the changes thus far to the remote repo. You can now use Netlify to create the bios.
Create a new hook called 'useBios##.tsx' in src/hooks/archivedBoardHooks/. Copy the same structure as in src/hooks/useBios.tsx.
You must change the following:
- name of the query ('query Bio' -> 'query Bio##')
- category search parameter ('eq: "bio" -> 'eq: "board20##"')
Create a new page called board##.tsx in src/pages/. Copy the same structure as in src/pages/board23.tsx.
You must change the following:
- 'import useBios23 from "hooks/archivedBoardHooks/useBios23"' -> 'import useBios## from "hooks/archivedBoardHooks/useBios##"'
- text in the returned JSX.Element
- 'query BoardPage23' -> 'query BoardPage##'
- 'eq: "board2023.JPG"' -> 'eq: "{filename in step 3}"'
Lastly, edit the src/pages/archive.tsx file to include a section for the new page. See example below:
.../>
<ArchiveSection
title="2023-2024"
boardLink="/board23"
eventsLink="/events"
/>
</...
Similar to adding events, go to the Netlify CMS admin panel. Click on the bios section on the side, and you should be able to add new bios and edit existing ones. You can choose their position with a drop-down menu. In order to add new positions, go to the "Editable Options" collection on the left side of the page and select the "Bio Positions Order" file. Inside that file is an array of all the possible positions! You can add new positions there. The order the positions are listed also determine their order on the site.
We get all the bios using the useBios.tsx
hook. Like the useEvents hook, it runs a static query that gets all the content labeled bio and links them together with their associated images. Then, we can use the Bio.tsx
component to display the data.
We can change how the bios look on the site by editing Bio.tsx
.
With Netlify, we can make our own link shortening service! The magic all happens in our _redirects
file. In that file, we can add a URL on our site, and redirect it to another URL! For example,
/l/fb https://www.facebook.com/umcptasa/
redirects from umcp-tase.netlify.app/l/fn
to the TASA Facebook page. I chose to add /l/
in front to make it clear that the link is shortened and not a page on our site. This isn't necessary, but it helps separate out links and ensure we can add shortened URLs with the same name as a page on our site.
Gatsby uses GraphQL to query data. We use gatsby-plugin-graphql-codegen to automatically generate types from the queries we make. Then, you can import the generated type from graphql types. You can see an example of this in the Logo
component.
import { LogoQuery } from "graphql-types"
const data =
useStaticQuery<LogoQuery >
graphql`
query Logo {
file(relativePath: { eq: "logo.png" }) {
childImageSharp {
fixed(width: 50, height: 50) {
...GatsbyImageSharpFixed
}
}
}
}
`
Note how the query itself is named on the second line, and how LogoQuery
is present as the type for useStaticQuery
. All generated types get stored in graphql-types.ts
https://www.typescriptlang.org/docs/handbook/module-resolution.html Change in tsconfig.json
When to use type vs interface for Typescript
That's pretty easy! Just set the position prop of the AppBar to "fixed" instead of "relative". If you want the header to respond to scroll actions (ex: changing from transparent to opaque), utilize the scrollTrigger boolean passed in from the Layout component. You can set different styles depending on whether or not scrollTrigger is true or false. Check out the commented code for an example.
This interface is based off of https://github.com/creativetimofficial/material-kit-react
In order to re-use the same query for all our background iamges, we create a fragment in ParallaxBackground.tsx
that can be used in all those queries.
export const imageQueryFragment = graphql`
fragment BackgroundImage on File {
childImageSharp {
fluid(quality: 100) {
...GatsbyImageSharpFluid
...GatsbyImageSharpFluidLimitPresentationSize
}
}
}
`
As described in the Gatsby docs on fragments, the ParallaxBackground
component won't actually use this query for data. But since Gatsby preprocesses all graphql queries, it'll generate this fragment that we can then use in other queries. The fragment name is BackgroundImage
and it can be used in queries under the type File
. For example, in index.tsx
we use the fragment by writing
export const query = graphql`
query HomePage {
mainBackground: file(relativePath: { eq: "Taiwan.jpg" }) {
...BackgroundImage
}
presidentBackground: file(relativePath: { eq: "bg10.jpg" }) {
...BackgroundImage
}
newsletterBackground: file(relativePath: { eq: "Taiwan2.jpg" }) {
...BackgroundImage
}
`
If styles look like they work on first load but don't on subsequent loads or vice versa, the problem is likely with server side rendering. In order to serve faster pages, Gatsby first pre-compiles during build the DOM from our React code on build. Gatsby uses the functions wrapRootElement and wrapPageElement in gatsby-ssr.js
during this time, which is why those two functions have to be the same as the ones in gatsby-browser.js
which is the file that determines what the site uses on the client side.
Because React uses rehydration instead of re-rendering to reconcile the differences between server and client side rendering, things can start to get wonky. Rehydration relies on the assumption that the DOM stays the same which sometimes isn't the case if we have dynamic content. So if styles look wonky between first load, which is what the server provides to the user, and the second load, where the client usually kicks in to render the page, it's likely that some element isn't in the right place in the DOM. To be honest, I'm not sure if this is exactly why things don't look right, but it seems to be the best explanation I've found.
That's where the ClientOnly
component comes in! It utilizes useEffect to only mount the component when the page is loaded. Since this only happens for the client, then the DOM stays the same between when the page is compiled during server-side rendering and when the client gets the page. Rehydration works, and then, the client can add the content that's missing after rehydration occurs. This is called two-pass rendering!
The code for the component as well as a better explanation about this issue can be found on Josh W Comeau's blog post The Perils of Rehydration