This is a template for the backend portion of a student organization website. It utilizes Netlify CMS to simplfy adding new events and editing bios. Plus, it comes with helpful link shortening! I left the actual UI and design of the site pretty minimal so that you can edit it however you please!
- Installation
- Files to Change
- Customizing Theme and Styling
- Mailchimp Integration
- Netlify CMS
- Adding New Pages
- Adding New Events
- Editing Bios
- Link Shortening
- GraphQL Type Generation
- Miscellaneous
Everything is already set-up in the package.json so all you have to do is
npm install
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 EventPreview.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.
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. Each bio has a section for site display order which determines what order the bios are shown on the bios page. It's a bit of a hacky way to do it right now since adding a position would require going through every bio and pushing up the positions after it up by one. An alternative is defining our own order in an array somewhere then sorting after we pull the data down from graphql. But then you can't change the order through Netlify CMS.
Position | Index |
---|---|
Co-President | 1 |
Secretary | 2 |
Internal VP | 3 |
External VP | 4 |
Treasurer | 5 |
Public Relations | 6 |
AASU Rep | 7 |
Historian | 8 |
Webmaster | 9 |
Co-Fundraising Chair | 10 |
Sports Chair | 11 |
Culture Chair | 12 |
Videographer | 13 |
ITASA Rep | 14 |
Senior Advisor | 15 |
Alumni Advisor | 16 |
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-typegen to automatically generate types from the queries we make. This plugin will automatically add types to any static queries we make. You can see an example of this in the Logo
component.
const data =
useStaticQuery <
GatsbyTypes.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 GatsbyTypes.LogoQuery
is present as the type for useStaticQuery
. All generated types get stored in src/__generated__/gatsby-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