{"id":511,"date":"2023-10-16T13:00:00","date_gmt":"2023-10-16T13:00:00","guid":{"rendered":"https:\/\/upprofits.net\/?p=511"},"modified":"2024-08-30T11:28:33","modified_gmt":"2024-08-30T11:28:33","slug":"gatsby-headaches-working-with-media-part-2","status":"publish","type":"post","link":"https:\/\/upprofits.net\/index.php\/2023\/10\/16\/gatsby-headaches-working-with-media-part-2\/","title":{"rendered":"Gatsby Headaches: Working With Media (Part 2)"},"content":{"rendered":"

Gatsby Headaches: Working With Media (Part 2)<\/title><\/p>\n<article>\n<header>\n<h1>Gatsby Headaches: Working With Media (Part 2)<\/h1>\n<address>Juan Diego Rodr\u00edguez<\/address>\n<p> 2023-10-16T13:00:00+00:00<br \/>\n 2024-08-30T10:05:08+00:00<br \/>\n <\/header>\n<p>Gatsby is a true Jamstack framework. It works with React-powered components that consume APIs before optimizing and bundling everything to serve as static files with bits of reactivity. That includes media files, like images, video, and audio.<\/p>\n<p>The problem is that there\u2019s no \u201cone\u201d way to handle media in a Gatsby project. We have plugins for everything, from making queries off your local filesystem and compressing files to inlining SVGs and serving images in the responsive image format.<\/p>\n<p>Which plugins should be used for certain types of media? How about certain use cases for certain types of media? That\u2019s where you might encounter headaches because there are many plugins — some official and some not — that are capable of handling one or more use cases — some outdated and some not.<\/p>\n<p>That is what this brief two-part series is about. In <a href=\"https:\/\/www.smashingmagazine.com\/2023\/10\/gatsby-headaches-working-media-part1\/\">Part 1<\/a>, we discussed various strategies and techniques for handling images, video, and audio in a Gatsby project.<\/p>\n<p>This time, in Part 2, we are covering a different type of media we commonly encounter: <strong>documents<\/strong>. Specifically, we will tackle considerations for Gatsby projects that make use of Markdown and PDF files. And before wrapping up, we will also demonstrate an approach for using 3D models.<\/p>\n<h2 id=\"solving-markdown-headaches-in-gatsby\">Solving Markdown Headaches In Gatsby<\/h2>\n<p>In Gatsby, Markdown files are commonly used to programmatically create pages, such as blog posts. You can write content in Markdown, parse it into your GraphQL data layer, source it into your components, and then bundle it as HTML static files during the build process.<\/p>\n<p>Let\u2019s learn how to load, query, and handle the Markdown for an existing page in Gatsby.<\/p>\n<h3 id=\"loading-and-querying-markdown-from-graphql\">Loading And Querying Markdown From GraphQL<\/h3>\n<p>The first step on your Gatsby project is to load the project\u2019s Markdown files to the GraphQL data layer. We can do this using the <code>gatsby-source-filesystem<\/code> plugin we used to query the local filesystem for image files in <a href=\"https:\/\/www.smashingmagazine.com\/2023\/10\/gatsby-headaches-working-media-part1\/\">Part 1<\/a> of this series.<\/p>\n<pre><code class=\"language-bash\">npm i gatsby-source-filesystem\n<\/code><\/pre>\n<p>In <code>gatsby-config.js<\/code>, we declare the folder where Markdown files will be saved in the project:<\/p>\n<pre><code class=\"language-javascript\">module.exports = {\n plugins: [\n {\n resolve: `gatsby-source-filesystem`,\n options: {\n name: `assets`,\n path: `${ __dirname }\/src\/assets`,\n },\n },\n ],\n};\n<\/code><\/pre>\n<p>Let\u2019s say that we have the following Markdown file located in the project\u2019s <code>.\/src\/assets<\/code> directory:<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-markdown\">---\ntitle: sample-markdown-file\ndate: 2023-07-29\n---\n\n# Sample Markdown File\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed consectetur imperdiet urna, vitae pellentesque mauris sollicitudin at. Sed id semper ex, ac vestibulum nunc. Etiam ,\n\n![A beautiful forest!](\/forest.jpg \"Forest trail\")\n\n```bash\nlorem ipsum dolor sit\n```\n\n## Subsection\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed consectetur imperdiet urna, vitae pellentesque mauris sollicitudin at. Sed id semper ex, ac vestibulum nunc. Etiam efficitur, nunc nec placerat dignissim, ipsum ante ultrices ante, sed luctus nisl felis eget ligula. Proin sed quam auctor, posuere enim eu, vulputate felis. Sed egestas, tortor\n<\/code><\/pre>\n<\/div>\n<p>This example consists of two main sections: the <strong>frontmatter<\/strong> and <strong>body<\/strong>. It is a common structure for Markdown files.<\/p>\n<ul>\n<li><strong>Frontmatter<\/strong><br \/>\nEnclosed in triple dashes (<code>---<\/code>), this is an optional section at the beginning of a Markdown file that contains metadata and configuration settings for the document. In our example, the frontmatter contains information about the page\u2019s <code>title<\/code> and <code>date<\/code>, which Gatsby can use as GraphQL arguments.<\/li>\n<li><strong>Body<\/strong><br \/>\nThis is the content that makes up the page\u2019s main body content.<\/li>\n<\/ul>\n<p>We can use the <a href=\"https:\/\/www.gatsbyjs.com\/plugins\/gatsby-transformer-remark\/\"><code>gatsby-transformer-remark<\/code><\/a> plugin to parse Markdown files to a GraphQL data layer. Once it is installed, we will need to register it in the project\u2019s <code>gatsby-config.js<\/code> file:<\/p>\n<pre><code class=\"language-javascript\">module.exports = {\n plugins: [\n {\n resolve: `gatsby-transformer-remark`,\n options: { },\n },\n ],\n};\n<\/code><\/pre>\n<p>Restart the development server and navigate to <code>http:\/\/localhost:8000\/___graphql<\/code> in the browser. Here, we can play around with Gatsby\u2019s data layer and check our Markdown file above by making a query using the <code>title<\/code> property (<code>sample-markdown-file<\/code>) in the frontmatter:<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-javascript\">query {\n markdownRemark(frontmatter: { title: { eq: \"sample-markdown-file\" } }) {\n html\n }\n}\n<\/code><\/pre>\n<\/div>\n<p>This should return the following result:<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-javascript\">{\n \"data\": {\n \"markdownRemark\": {\n \"html\": \"<h1>Sample Markdown File<\/h1>n<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed consectetur imperdiet urna, vitae pellentesque mauris sollicitudin at.\"\n \/\/ etc.\n }\n },\n \"extensions\": {}\n}\n<\/code><\/pre>\n<\/div>\n<p>Notice that the <strong>content in the response is formatted in HTML<\/strong>. We can also query the original body as <code>rawMarkdownBody<\/code> or any of the frontmatter attributes.<\/p>\n<p>Next, let\u2019s turn our attention to approaches for handling Markdown content once it has been queried.<\/p>\n<div data-audience=\"non-subscriber\" data-remove=\"true\" class=\"feature-panel-container\">\n<aside class=\"feature-panel\">\n<div class=\"feature-panel-left-col\">\n<div class=\"feature-panel-description\">\n<p>Meet <strong><a data-instant href=\"\/printed-books\/touch-design-for-mobile-interfaces\/\">Touch Design for Mobile Interfaces<\/a><\/strong>, Steven Hoober\u2019s brand-new guide on <strong>designing for mobile<\/strong> with proven, universal, human-centric guidelines. <strong>400 pages<\/strong>, jam-packed with in-depth user research and <strong>best practices<\/strong>.<\/p>\n<p><a data-instant href=\"https:\/\/www.smashingmagazine.com\/printed-books\/touch-design-for-mobile-interfaces\/\" class=\"btn btn--green btn--large\">Jump to table of contents \u21ac<\/a><\/div>\n<\/div>\n<div class=\"feature-panel-right-col\"><a data-instant href=\"https:\/\/www.smashingmagazine.com\/printed-books\/touch-design-for-mobile-interfaces\/\" class=\"feature-panel-image-link\"><\/p>\n<div class=\"feature-panel-image\">\n<img decoding=\"async\" loading=\"lazy\" class=\"feature-panel-image-img\" src=\"https:\/\/archive.smashing.media\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/b14658fc-bb2d-41a6-8d1a-70eaaf1b8ec8\/touch-design-book-shop-opt.png\" alt=\"Feature Panel\" width=\"480\" height=\"697\" \/><\/p>\n<\/div>\n<p><\/a>\n<\/div>\n<\/aside>\n<\/div>\n<h3 id=\"using-dangerouslysetinnerhtml\">Using <code>DangerouslySetInnerHTML<\/code><\/h3>\n<p><code>dangerouslySetInnerHTML<\/code> is a React feature that injects raw HTML content into a component\u2019s rendered output by overriding the <code>innerHTML<\/code> property of the DOM node. It\u2019s considered <em>dangerous<\/em> since it essentially bypasses React\u2019s built-in mechanisms for rendering and sanitizing content, opening up the possibility of <strong>cross-site scripting (XSS)<\/strong> attacks without paying special attention.<\/p>\n<p>That said, if you need to render HTML content dynamically but want to avoid the risks associated with <code>dangerouslySetInnerHTML<\/code>, consider using libraries that sanitize HTML input <em>before<\/em> rendering it, such as <a href=\"https:\/\/github.com\/cure53\/DOMPurify\"><code>dompurify<\/code><\/a>.<\/p>\n<p>The <code>dangerouslySetInnerHTML<\/code> prop takes an <code>__html<\/code> object with a single key that should contain the raw HTML content. Here\u2019s an example:<\/p>\n<pre><code class=\"language-javascript\">const DangerousComponent = () => {\n const rawHTML = \"<p>This is <em>dangerous<\/em> content!<\/p>\";\n\n return <div dangerouslySetInnerHTML={ { __html: rawHTML } } \/>;\n};\n<\/code><\/pre>\n<p>To display Markdown using <code>dangerouslySetInnerHTML<\/code> in a Gatsby project, we need first to query the HTML string using Gatsby\u2019s <code>useStaticQuery<\/code> hook:<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-javascript\">import * as React from \"react\";\nimport { useStaticQuery, graphql } from \"gatsby\";\n\nconst DangerouslySetInnerHTML = () => {\n const data = useStaticQuery(graphql`\n query {\n markdownRemark(frontmatter: { title: { eq: \"sample-markdown-file\" } }) {\n html\n }\n }\n `);\n\n return <div><\/div>;\n};\n<\/code><\/pre>\n<\/div>\n<p>Now, the <code>html<\/code> property can be injected into the <code>dangerouslySetInnerHTML<\/code> prop.<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-javascript\">import * as React from \"react\";\nimport { useStaticQuery, graphql } from \"gatsby\";\n\nconst DangerouslySetInnerHTML = () => {\n const data = useStaticQuery(graphql`\n query {\n markdownRemark(frontmatter: { title: { eq: \"sample-markdown-file\" } }) {\n html\n }\n }\n `);\n\n const markup = { __html: data.markdownRemark.html };\n\n return <div dangerouslySetInnerHTML={ markup }><\/div>;\n};\n<\/code><\/pre>\n<\/div>\n<p>This might look OK at first, but if we were to open the browser to view the content, we would notice that the image declared in the Markdown file is missing from the output. We never told Gatsby to parse it. We do have two options to include it in the query, each with pros and cons:<\/p>\n<ol>\n<li><strong>Use a plugin to parse Markdown images.<\/strong><br \/>\nThe <code>gatsby-remark-images<\/code> plugin is capable of processing Markdown images, making them available when querying the Markdown from the data layer. The main downside is the extra configuration it requires to set and render the files. Besides, Markdown images parsed with this plugin only will be available as HTML, so we would need to select a package that can render HTML content into React components, such as <a href=\"https:\/\/www.npmjs.com\/package\/rehype-react\"><code>rehype-react<\/code><\/a>.<\/li>\n<li><strong>Save images in the <code>static<\/code> folder.<\/strong><br \/>\nThe <code>\/static<\/code> folder at the root of a Gatsby project can store assets that won\u2019t be parsed by webpack but will be available in the <code>public<\/code> directory. Knowing this, we can point Markdown images to the <code>\/static<\/code> directory, and they will be available anywhere in the client. The disadvantage? We are unable to leverage Gatsby\u2019s image optimization features to minimize the overall size of the bundled package in the build process.<\/li>\n<\/ol>\n<p><strong>The <code>gatsby-remark-images<\/code> approach is probably most suited for larger projects since it is more manageable<\/strong> than saving all Markdown images in the <code>\/static<\/code> folder.<\/p>\n<p>Let\u2019s assume that we have decided to go with the second approach of saving images to the <code>\/static<\/code> folder. To reference an image in the <code>\/static<\/code> directory, we just point to the filename without any special argument on the path.<\/p>\n<pre><code class=\"language-javascript\">const StaticImage = () => {\n return <img src={ \"\/desert.png\" } alt=\"Desert\" \/>;\n};\n<\/code><\/pre>\n<h3 id=\"react-markdown\"><code>react-markdown<\/code><\/h3>\n<p>The <code>react-markdown<\/code> package provides a component that renders markdown into React components, avoiding the risks of using <code>dangerouslySetInnerHTML<\/code>. The component uses a syntax tree to build the virtual DOM, which allows for updating only the changing DOM instead of completely overwriting it. And since it uses <a href=\"https:\/\/github.com\/remarkjs\/remark\"><code>remark<\/code><\/a>, we can combine <code>react-markdown<\/code> with <code>remark<\/code>\u2019s vast <a href=\"https:\/\/github.com\/remarkjs\/remark\/blob\/main\/doc\/plugins.md\">plugin ecosystem<\/a>.<\/p>\n<p>Let\u2019s install the package:<\/p>\n<pre><code class=\"language-bash\">npm i react-markdown\n<\/code><\/pre>\n<p>Next, we replace our prior example with the <code>ReactMarkdown<\/code> component. However, instead of querying for the <code>html<\/code> property this time, we will query for <code>rawMarkdownBody<\/code> and then pass the result to <code>ReactMarkdown<\/code> to render it in the DOM.<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-javascript\">import * as React from \"react\";\nimport ReactMarkdown from \"react-markdown\";\nimport { useStaticQuery, graphql } from \"gatsby\";\n\nconst MarkdownReact = () => {\n const data = useStaticQuery(graphql`\n query {\n markdownRemark(frontmatter: { title: { eq: \"sample-markdown-file\" } }) {\n rawMarkdownBody\n }\n }\n `);\n\n return <ReactMarkdown>{data.markdownRemark.rawMarkdownBody}<\/ReactMarkdown>;\n};\n<\/code><\/pre>\n<\/div>\n<h3 id=\"markdown-to-jsx\"><code>markdown-to-jsx<\/code><\/h3>\n<p><code>markdown-to-jsx<\/code> is the most popular Markdown component — and the lightest since it comes without any dependencies. It\u2019s an excellent tool to consider when aiming for performance, and it does not require <code>remark<\/code>\u2019s plugin ecosystem. The plugin works much the same as the <code>react-markdown<\/code> package, only this time, we import a <code>Markdown<\/code> component instead of <code>ReactMarkdown<\/code>.<\/p>\n<pre><code class=\"language-bash\">npm i markdown-to-jsx\n<\/code><\/pre>\n<div class=\"break-out\">\n<pre><code class=\"language-javascript\">import * as React from \"react\";\nimport Markdown from \"markdown-to-jsx\";\nimport { useStaticQuery, graphql } from \"gatsby\";\n\nconst MarkdownToJSX = () => {\n const data = useStaticQuery(graphql`\n query {\n markdownRemark(frontmatter: { title: { eq: \"sample-markdown-file\" } }) {\n rawMarkdownBody\n }\n }\n `);\n\n return <Markdown> { data.markdownRemark.rawMarkdownBody }<\/Markdown>;\n};\n<\/code><\/pre>\n<\/div>\n<p>We have taken raw Markdown and parsed it as JSX. But what if we don\u2019t necessarily want to parse it at all? We will look at that use case next.<\/p>\n<h3 id=\"react-md-editor\"><code>react-md-editor<\/code><\/h3>\n<p>Let\u2019s assume for a moment that we are creating a lightweight CMS and want to give users the option to write posts in Markdown. In this case, instead of parsing the Markdown to HTML, we need to query it as-is.<\/p>\n<p>Rather than creating a Markdown editor from scratch to solve this, several packages are capable of handling the raw Markdown for us. My personal favorite is<br \/>\n<a href=\"https:\/\/github.com\/uiwjs\/react-md-editor\"><code>react-md-editor<\/code><\/a>.<\/p>\n<p>Let\u2019s install the package:<\/p>\n<pre><code class=\"language-bash\">npm i @uiw\/react-md-editor\n<\/code><\/pre>\n<p>The <code>MDEditor<\/code> component can be imported and set up as a <a href=\"https:\/\/maxschmitt.me\/posts\/react-components-controlled-uncontrolled\">controlled component<\/a>:<\/p>\n<pre><code class=\"language-javascript\">import * as React from \"react\";\nimport { useState } from \"react\";\nimport MDEditor from \"@uiw\/react-md-editor\";\n\nconst ReactMDEditor = () => {\n const [value, setValue] = useState(\"**Hello world!!!**\");\n\n return <MDEditor value={ value } onChange={ setValue } \/>;\n};\n<\/code><\/pre>\n<p>The plugin also comes with a built-in <code>MDEditor.Markdown<\/code> component used to preview the rendered content:<\/p>\n<pre><code class=\"language-javascript\">import * as React from \"react\";\nimport { useState } from \"react\";\nimport MDEditor from \"@uiw\/react-md-editor\";\n\nconst ReactMDEditor = () => {\n const [value, setValue] = useState(\"**Hello world!**\");\n\n return (\n <>\n <MDEditor value={value} onChange={ setValue } \/>\n <MDEditor.Markdown source={ value } \/>\n <\/>\n );\n};\n<\/code><\/pre>\n<figure class=\"\n \n break-out article__image\n \n \n \"><\/p>\n<p> <a href=\"https:\/\/files.smashing.media\/articles\/gatsby-headaches-working-media-part2\/react-md-editor-result.png\"><\/p>\n<p> <img decoding=\"async\" loading=\"lazy\" width=\"800\" height=\"432\" src=\"https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_80\/w_400\/https:\/\/files.smashing.media\/articles\/gatsby-headaches-working-media-part2\/react-md-editor-result.png\" alt=\"Markdown previewer\" \/><\/p>\n<p> <\/a><figcaption class=\"op-vertical-bottom\">\n The plugin includes a feature that shows a preview of the rendered Markdown. (<a href=\"https:\/\/files.smashing.media\/articles\/gatsby-headaches-working-media-part2\/react-md-editor-result.png\">Large preview<\/a>)<br \/>\n <\/figcaption><\/figure>\n<p>That was a look at various headaches you might encounter when working with Markdown files in Gatsby. Next, we are turning our attention to another type of file, PDF.<\/p>\n<div class=\"partners__lead-place\"><\/div>\n<h2 id=\"solving-pdf-headaches-in-gatsby\">Solving PDF Headaches In Gatsby<\/h2>\n<p>PDF files handle content with a completely different approach to Markdown files. With Markdown, we simplify the content to its most raw form so it can be easily handled across different front ends. PDFs, however, are the content presented to users on the front end. Rather than extracting the raw content from the file, we want the user to see it as it is, often by making it available for download or embedding it in a way that the user views the contents directly on the page, sort of like a video.<\/p>\n<p>I want to show you four approaches to consider when embedding a PDF file on a page in a Gatsby project.<\/p>\n<h3 id=\"using-the-iframe-element\">Using The <code><iframe><\/code> Element<\/h3>\n<p>The easiest way to embed a PDF into your Gatsby project is perhaps through an <code>iframe<\/code> element:<\/p>\n<pre><code class=\"language-javascript\">import * as React from \"react\";\nimport samplePDF from \".\/assets\/lorem-ipsum.pdf\";\n\nconst IframePDF = () => {\n return <iframe src={ samplePDF }><\/iframe>;\n};\n<\/code><\/pre>\n<p>It\u2019s worth calling out here that the <code>iframe<\/code> element supports lazy loading (<code>loading="lazy"<\/code>) to boost performance in instances where it doesn\u2019t need to load right away.<\/p>\n<h3 id=\"embedding-a-third-party-viewer\">Embedding A Third-Party Viewer<\/h3>\n<p>There are situations where PDFs are more manageable when stored in a third-party service, such as Drive, which includes a PDF viewer that can embedded directly on the page. In these cases, we can use the same <code>iframe<\/code> we used above, but with the source pointed at the service.<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-javascript\">import * as React from \"react\";\n\nconst ThirdPartyIframePDF = () => {\n return (\n <iframe\n src=\"https:\/\/drive.google.com\/file\/d\/1IiRZOGib_0cZQY9RWEDslMksRykEnrmC\/preview\"\n allowFullScreen\n title=\"PDF Sample in Drive\"\n \/>\n );\n};\n<\/code><\/pre>\n<\/div>\n<p>It\u2019s a good reminder that you want to trust the third-party content that\u2019s served in an <code>iframe<\/code>. If we\u2019re effectively loading a document from someone else\u2019s source that we do not control, your site could become prone to security vulnerabilities should that source become compromised.<\/p>\n<h3 id=\"using-react-pdf\">Using <code>react-pdf<\/code><\/h3>\n<p>The <code>react-pdf<\/code> package provides an interface to render PDFs as React components. It is based on <a href=\"https:\/\/github.com\/mozilla\/pdf.js\/\"><code>pdf.js<\/code><\/a>, a JavaScript library that renders PDFs using <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTML\/Element\/canvas\">HTML Canvas<\/a>.<\/p>\n<p>To display a PDF file on a <code><canvas><\/code>, the <code>react-pdf<\/code> library exposes the <code>Document<\/code> and <code>Page<\/code> components:<\/p>\n<ul>\n<li><strong><code>Document<\/code><\/strong>: Loads the PDF passed in its <code>file<\/code> prop.<\/li>\n<li><strong><code>Page<\/code><\/strong>: Displays the page passed in its <code>pageNumber<\/code> prop. It should be placed inside <code>Document<\/code>.<\/li>\n<\/ul>\n<p>We can install to our project:<\/p>\n<pre><code class=\"language-bash\">npm i react-pdf\n<\/code><\/pre>\n<p>Before we put <code>react-pdf<\/code> to use, we will need to set up a service worker for <code>pdf.js<\/code> to process time-consuming tasks such as parsing and rendering a PDF document.<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-javascript\">import * as React from \"react\";\nimport { pdfjs } from \"react-pdf\";\n\npdfjs.GlobalWorkerOptions.workerSrc = \"https:\/\/unpkg.com\/pdfjs-dist@3.6.172\/build\/pdf.worker.min.js\";\n\nconst ReactPDF = () => {\n return <div><\/div>;\n};\n<\/code><\/pre>\n<\/div>\n<p>Now, we can import the <code>Document<\/code> and <code>Page<\/code> components, passing the PDF file to their props. We can also import the component\u2019s necessary styles while we are at it.<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-javascript\">import * as React from \"react\";\nimport { Document, Page } from \"react-pdf\";\n\nimport { pdfjs } from \"react-pdf\";\nimport \"react-pdf\/dist\/esm\/Page\/AnnotationLayer.css\";\nimport \"react-pdf\/dist\/esm\/Page\/TextLayer.css\";\n\nimport samplePDF from \".\/assets\/lorem-ipsum.pdf\";\n\npdfjs.GlobalWorkerOptions.workerSrc = \"https:\/\/unpkg.com\/pdfjs-dist@3.6.172\/build\/pdf.worker.min.js\";\n\nconst ReactPDF = () => {\n return (\n <Document file={ samplePDF }>\n <Page pageNumber={ 1 } \/>\n <\/Document>\n );\n};\n<\/code><\/pre>\n<\/div>\n<p>Since accessing the PDF will change the current page, we can add state management by passing the current <code>pageNumber<\/code> to the <code>Page<\/code> component:<\/p>\n<pre><code class=\"language-javascript\">import { useState } from \"react\";\n\n\/\/ ...\n\nconst ReactPDF = () => {\n const [currentPage, setCurrentPage] = useState(1);\n\n return (\n <Document file={ samplePDF }>\n <Page pageNumber={ currentPage } \/>\n <\/Document>\n );\n};\n<\/code><\/pre>\n<p>One issue is that we have pagination but don\u2019t have a way to navigate between pages. We can change that by adding controls. First, we will need to know the number of pages in the document, which is accessed on the <code>Document<\/code> component\u2019s <code>onLoadSuccess<\/code> event:<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-javascript\">\/\/ ...\n\nconst ReactPDF = () => {\n const [pageNumber, setPageNumber] = useState(null);\n const [currentPage, setCurrentPage] = useState(1);\n\n const handleLoadSuccess = ({ numPages }) => {\n setPageNumber(numPages);\n };\n\n return (\n <Document file={ samplePDF } onLoadSuccess={ handleLoadSuccess }>\n <Page pageNumber={ currentPage } \/>\n <\/Document>\n );\n};\n<\/code><\/pre>\n<\/div>\n<p>Next, we display the current page number and add \u201cNext\u201d and \u201cPrevious\u201d buttons with their respective handlers to change the current page:<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-javascript\">\/\/ ...\n\nconst ReactPDF = () => {\n const [currentPage, setCurrentPage] = useState(1);\n const [pageNumber, setPageNumber] = useState(null);\n\n const handlePrevious = () => {\n \/\/ checks if it isn't the first page\n if (currentPage > 1) {\n setCurrentPage(currentPage - 1);\n }\n };\n\n const handleNext = () => {\n \/\/ checks if it isn't the last page\n if (currentPage < pageNumber) {\n setCurrentPage(currentPage + 1);\n }\n };\n\n const handleLoadSuccess = ({ numPages }) => {\n setPageNumber(numPages);\n };\n\n return (\n <div>\n <Document file={ samplePDF } onLoadSuccess={ handleLoadSuccess }>\n <Page pageNumber={ currentPage } \/>\n <\/Document>\n <button onClick={ handlePrevious }>Previous<\/button>\n <p>{currentPage}<\/p>\n <button onClick={ handleNext }>Next<\/button>\n <\/div>\n );\n};\n<\/code><\/pre>\n<\/div>\n<p>This provides us with everything we need to embed a PDF file on a page via the HTML <code><canvas><\/code> element using <code>react-pdf<\/code> and <code>pdf.js<\/code>.<\/p>\n<p>There is another similar package capable of embedding a PDF file in a viewer, complete with pagination controls. We\u2019ll look at that next.<\/p>\n<h3 id=\"using-react-pdf-viewer\">Using <code>react-pdf-viewer<\/code><\/h3>\n<p>Unlike <code>react-pdf<\/code>, the <code>react-pdf-viewer<\/code> package provides built-in customizable controls right out of the box, which makes embedding a multi-page PDF file a lot easier than having to import them separately.<\/p>\n<p>Let\u2019s install it:<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-bash\">npm i @react-pdf-viewer\/core@3.12.0 @react-pdf-viewer\/default-layout\n<\/code><\/pre>\n<\/div>\n<p>Since <code>react-pdf-viewer<\/code> also relies on <code>pdf.js<\/code>, we will need to create a service worker as we did with <code>react-pdf<\/code>, but only if we are not using both packages at the same time. This time, we are using a <code>Worker<\/code> component with a <code>workerUrl<\/code> prop directed at the worker\u2019s package.<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-javascript\">import * as React from \"react\";\nimport { Worker } from \"@react-pdf-viewer\/core\";\n\nconst ReactPDFViewer = () => {\n return (\n <>\n <Worker workerUrl=\"https:\/\/unpkg.com\/pdfjs-dist@3.4.120\/build\/pdf.worker.min.js\"><\/Worker>\n <\/>\n );\n};\n<\/code><\/pre>\n<\/div>\n<p>Note that a <strong>worker like this ought to be set just once at the layout level<\/strong>. This is especially true if you intend to use the PDF viewer across different pages.<\/p>\n<p>Next, we import the <code>Viewer<\/code> component with its styles and point it at the PDF through its <code>fileUrl<\/code> prop.<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-javascript\">import * as React from \"react\";\nimport { Viewer, Worker } from \"@react-pdf-viewer\/core\";\n\nimport \"@react-pdf-viewer\/core\/lib\/styles\/index.css\";\n\nimport samplePDF from \".\/assets\/lorem-ipsum.pdf\";\n\nconst ReactPDFViewer = () => {\n return (\n <>\n <Viewer fileUrl={ samplePDF } \/>\n <Worker workerUrl=\"https:\/\/unpkg.com\/pdfjs-dist@3.6.172\/build\/pdf.worker.min.js\"><\/Worker>\n <\/>\n );\n};\n<\/code><\/pre>\n<\/div>\n<p>Once again, we need to add controls. We can do that by importing the <code>defaultLayoutPlugin<\/code> (including its corresponding styles), making an instance of it, and passing it in the <code>Viewer<\/code> component\u2019s <code>plugins<\/code> prop.<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-javascript\">import * as React from \"react\";\nimport { Viewer, Worker } from \"@react-pdf-viewer\/core\";\nimport { defaultLayoutPlugin } from \"@react-pdf-viewer\/default-layout\";\n\nimport \"@react-pdf-viewer\/core\/lib\/styles\/index.css\";\nimport \"@react-pdf-viewer\/default-layout\/lib\/styles\/index.css\";\n\nimport samplePDF from \".\/assets\/lorem-ipsum.pdf\";\n\nconst ReactPDFViewer = () => {\n const defaultLayoutPluginInstance = defaultLayoutPlugin();\n\n return (\n <>\n <Viewer fileUrl={ samplePDF } plugins={ [defaultLayoutPluginInstance] } \/>\n <Worker workerUrl=\"https:\/\/unpkg.com\/pdfjs-dist@3.6.172\/build\/pdf.worker.min.js\"><\/Worker>\n <\/>\n );\n};\n<\/code><\/pre>\n<\/div>\n<p>Again, <code>react-pdf-viewer<\/code> is an alternative to <code>react-pdf<\/code> that can be a little easier to implement if you don\u2019t need full control over your PDF files, just the embedded viewer.<\/p>\n<p>There is one more plugin that provides an embedded viewer for PDF files. We will look at it, but only briefly, because I personally do not recommend using it in favor of the other approaches we\u2019ve covered.<\/p>\n<h3 id=\"why-you-shouldn-t-use-react-file-viewer\">Why You Shouldn\u2019t Use <code>react-file-viewer<\/code><\/h3>\n<p>The last plugin we will check out is <code>react-file-viewer<\/code>, a package that offers an embedded viewer with a simple interface but with the capacity to handle a variety of media in addition to PDF files, including images, videos, PDFs, documents, and spreadsheets.<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-javascript\">import * as React from \"react\";\nimport FileViewer from \"react-file-viewer\";\n\nconst PDFReactFileViewer = () => {\n return <FileViewer fileType=\"pdf\" filePath=\"\/lorem-ipsum.pdf\" \/>;\n};\n<\/code><\/pre>\n<\/div>\n<p>While <code>react-file-viewer<\/code> will get the job done, <strong>it is extremely outdated<\/strong> and could easily create more headaches than it solves with compatibility issues. I suggest avoiding it in favor of either an <code>iframe<\/code>, <code>react-pdf<\/code>, or <code>react-pdf-viewer<\/code>.<\/p>\n<div class=\"partners__lead-place\"><\/div>\n<h2 id=\"solving-3d-model-headaches-in-gatsby\">Solving 3D Model Headaches In Gatsby<\/h2>\n<p>I want to cap this brief two-part series with one more media type that might cause headaches in a Gatsby project: <strong>3D models<\/strong>.<\/p>\n<p>A 3D model file is a digital representation of a three-dimensional object that stores information about the object\u2019s geometry, texture, shading, and other properties of the object. On the web, 3D model files are used to enhance user experiences by bringing interactive and immersive content to websites. You are most likely to encounter them in product visualizations, architectural walkthroughs, or educational simulations.<\/p>\n<p>There is a multitude of 3D model formats, including glTF OBJ, FBX, STL, and so on. We will use glTF models for a demonstration of a headache-free 3D model implementation in Gatsby.<\/p>\n<p>The <a href=\"https:\/\/github.com\/KhronosGroup\/glTF\"><strong>GL Transmission Format<\/strong><\/a> <strong>(glTF)<\/strong> was designed specifically for the web and real-time applications, making it ideal for our example. Using glTF files does require a specific webpack loader, so for simplicity\u2019s sake, we will save the glTF model in the <code>\/static<\/code> folder at the root of our project as we look at two approaches to create the 3D visual with Three.js:<\/p>\n<ol>\n<li>Using a vanilla implementation of Three.js,<\/li>\n<li>Using a package that integrates Three.js as a React component.<\/li>\n<\/ol>\n<h3 id=\"using-three-js\">Using Three.js<\/h3>\n<p><a href=\"https:\/\/threejs.org\/\">Three.js<\/a> creates and loads interactive 3D graphics directly on the web with the help of <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/WebGL_API\">WebGL<\/a>, a JavaScript API for rendering 3D graphics in real-time inside HTML <code><canvas><\/code> elements.<\/p>\n<p>Three.js is not integrated with React or Gatsby out of the box, so we must modify our code to support it. A Three.js tutorial is out of scope for what we are discussing in this article, although excellent learning resources are available in the <a href=\"https:\/\/threejs.org\/docs\/\">Three.js documentation<\/a>.<\/p>\n<p>We start by installing the <code>three<\/code> library to the Gatsby project:<\/p>\n<pre><code class=\"language-bash\">npm i three\n<\/code><\/pre>\n<p>Next, we write a function to load the glTF model for Three.js to reference it. This means we need to import a <code>GLTFLoader<\/code> add-on to instantiate a new <code>loader<\/code> object.<\/p>\n<pre><code class=\"language-javascript\">import * as React from \"react\";\nimport * as THREE from \"three\";\n\nimport { GLTFLoader } from \"three\/addons\/loaders\/GLTFLoader.js\";\n\nconst loadModel = async (scene) => {\n const loader = new GLTFLoader();\n};\n<\/code><\/pre>\n<p>We use the <code>scene<\/code> object as a parameter in the <code>loadModel<\/code> function so we can attach our 3D model once loaded to the scene.<\/p>\n<p>From here, we use <code>loader.load()<\/code> which takes four arguments:<\/p>\n<ol>\n<li>The glTF file location,<\/li>\n<li>A callback when the resource is loaded,<\/li>\n<li>A callback while loading is in progress,<\/li>\n<li>A callback for handling errors.<\/li>\n<\/ol>\n<div class=\"break-out\">\n<pre><code class=\"language-javascript\">import * as React from \"react\";\nimport * as THREE from \"three\";\n\nimport { GLTFLoader } from \"three\/addons\/loaders\/GLTFLoader.js\";\n\nconst loadModel = async (scene) => {\n const loader = new GLTFLoader();\n\n await loader.load(\n \"\/strawberry.gltf\", \/\/ glTF file location\n function (gltf) {\n \/\/ called when the resource is loaded\n scene.add(gltf.scene);\n },\n undefined, \/\/ called while loading is in progress, but we are not using it\n function (error) {\n \/\/ called when loading returns errors\n console.error(error);\n }\n );\n};\n<\/code><\/pre>\n<\/div>\n<p>Let\u2019s create a component to host the scene and load the 3D model. We need to know the element\u2019s client <code>width<\/code> and <code>height<\/code>, which we can get using React\u2019s <code>useRef<\/code> hook to access the element\u2019s DOM properties.<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-javascript\">import * as React from \"react\";\nimport * as THREE from \"three\";\n\nimport { useRef, useEffect } from \"react\";\n\n\/\/ ...\n\nconst ThreeLoader = () => {\n const viewerRef = useRef(null);\n\n return <div style={ { height: 600, width: \"100%\" } } ref={ viewerRef }><\/div>; \/\/ Gives the element its dimensions\n};\n<\/code><\/pre>\n<\/div>\n<p>Since we are using the element\u2019s <code>clientWidth<\/code> and <code>clientHeight<\/code> properties, we need to create the scene on the client side inside React\u2019s <code>useEffect<\/code> hook where we configure the Three.js <code>scene<\/code> with its necessary complements, e.g., a camera, the WebGL renderer, and lights.<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-javascript\">useEffect(() => {\n const { current: viewer } = viewerRef;\n\n const scene = new THREE.Scene();\n\n const camera = new THREE.PerspectiveCamera(75, viewer.clientWidth \/ viewer.clientHeight, 0.1, 1000);\n\n const renderer = new THREE.WebGLRenderer();\n\n renderer.setSize(viewer.clientWidth, viewer.clientHeight);\n\n const ambientLight = new THREE.AmbientLight(0xffffff, 0.4);\n scene.add(ambientLight);\n\n const directionalLight = new THREE.DirectionalLight(0xffffff);\n directionalLight.position.set(0, 0, 5);\n scene.add(directionalLight);\n\n viewer.appendChild(renderer.domElement);\n renderer.render(scene, camera);\n}, []);\n<\/code><\/pre>\n<\/div>\n<p>Now we can invoke the <code>loadModel<\/code> function, passing the scene to it as the only argument:<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-javascript\">useEffect(() => {\n const { current: viewer } = viewerRef;\n\n const scene = new THREE.Scene();\n\n const camera = new THREE.PerspectiveCamera(75, viewer.clientWidth \/ viewer.clientHeight, 0.1, 1000);\n\n const renderer = new THREE.WebGLRenderer();\n\n renderer.setSize(viewer.clientWidth, viewer.clientHeight);\n\n const ambientLight = new THREE.AmbientLight(0xffffff, 0.4);\n scene.add(ambientLight);\n\n const directionalLight = new THREE.DirectionalLight(0xffffff);\n directionalLight.position.set(0, 0, 5);\n scene.add(directionalLight);\n\n loadModel(scene); \/\/ Here!\n\n viewer.appendChild(renderer.domElement);\n renderer.render(scene, camera);\n}, []);\n<\/code><\/pre>\n<\/div>\n<p>The last part of this vanilla Three.js implementation is to add <code>OrbitControls<\/code> that allow users to navigate the model. That might look something like this:<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-javascript\">import * as React from \"react\";\nimport * as THREE from \"three\";\n\nimport { useRef, useEffect } from \"react\";\nimport { OrbitControls } from \"three\/examples\/jsm\/controls\/OrbitControls\";\nimport { GLTFLoader } from \"three\/addons\/loaders\/GLTFLoader.js\";\n\nconst loadModel = async (scene) => {\n const loader = new GLTFLoader();\n\n await loader.load(\n \"\/strawberry.gltf\", \/\/ glTF file location\n function (gltf) {\n \/\/ called when the resource is loaded\n scene.add(gltf.scene);\n },\n undefined, \/\/ called while loading is in progress, but it is not used\n function (error) {\n \/\/ called when loading has errors\n console.error(error);\n }\n );\n};\n\nconst ThreeLoader = () => {\n const viewerRef = useRef(null);\n\n useEffect(() => {\n const { current: viewer } = viewerRef;\n\n const scene = new THREE.Scene();\n\n const camera = new THREE.PerspectiveCamera(75, viewer.clientWidth \/ viewer.clientHeight, 0.1, 1000);\n\n const renderer = new THREE.WebGLRenderer();\n\n renderer.setSize(viewer.clientWidth, viewer.clientHeight);\n\n const ambientLight = new THREE.AmbientLight(0xffffff, 0.4);\n scene.add(ambientLight);\n\n const directionalLight = new THREE.DirectionalLight(0xffffff);\n directionalLight.position.set(0, 0, 5);\n scene.add(directionalLight);\n\n loadModel(scene);\n\n const target = new THREE.Vector3(-0.5, 1.2, 0);\n const controls = new OrbitControls(camera, renderer.domElement);\n controls.target = target;\n\n viewer.appendChild(renderer.domElement);\n\n var animate = function () {\n requestAnimationFrame(animate);\n controls.update();\n renderer.render(scene, camera);\n };\n animate();\n }, []);\n\n <div style={ { height: 600, width: \"100%\" } } ref={ viewerRef }><\/div>;\n};\n<\/code><\/pre>\n<\/div>\n<p>That is a straight Three.js implementation in a Gatsby project. Next is another approach using a library.<\/p>\n<h3 id=\"using-react-three-fiber\">Using React Three Fiber<\/h3>\n<p><code>react-three-fiber<\/code> is a library that integrates the Three.js with React. One of its advantages over the vanilla Three.js approach is its ability to manage and update 3D scenes, making it easier to compose scenes without manually handling intricate aspects of Three.js.<\/p>\n<p>We begin by installing the library to the Gatsby project:<\/p>\n<pre><code class=\"language-bash\">npm i react-three-fiber @react-three\/drei\n<\/code><\/pre>\n<p>Notice that the installation command includes the <code>@react-three\/drei<\/code> package, which we will use to add controls to the 3D viewer.<\/p>\n<p>I personally love <code>react-three-fiber<\/code> for being tremendously self-explanatory. For example, I had a relatively easy time migrating the extensive chunk of code from the vanilla approach to this much cleaner code:<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-javascript\">import * as React from \"react\";\nimport { useLoader, Canvas } from \"@react-three\/fiber\";\nimport { OrbitControls } from \"@react-three\/drei\";\nimport { GLTFLoader } from \"three\/examples\/jsm\/loaders\/GLTFLoader\";\n\nconst ThreeFiberLoader = () => {\n const gltf = useLoader(GLTFLoader, \"\/strawberry.gltf\");\n\n return (\n <Canvas camera={ { fov: 75, near: 0.1, far: 1000, position: [5, 5, 5] } } style={ { height: 600, width: \"100%\" } }>\n <ambientLight intensity={ 0.4 } \/>\n <directionalLight color=\"white\" \/>\n <primitive object={ gltf.scene } \/>\n <OrbitControls makeDefault \/>\n <\/Canvas>\n );\n};\n<\/code><\/pre>\n<\/div>\n<p>Thanks to <code>react-three-fiber<\/code>, we get the same result as a vanilla Three.js implementation but with fewer steps, more efficient code, and a slew of abstractions for managing and updating Three.js scenes.<\/p>\n<h2 id=\"two-final-tips\">Two Final Tips<\/h2>\n<p>The last thing I want to leave you with is two final considerations to take into account when working with media files in a Gatsby project.<\/p>\n<h3 id=\"bundling-assets-via-webpack-and-the-static-folder\">Bundling Assets Via Webpack And The <code>\/static<\/code> Folder<\/h3>\n<p>Importing an asset as a module so it can be bundled by webpack is a common strategy to add post-processing and minification, as well as hashing paths on the client. But there are two additional use cases where you might want to avoid it altogether and use the <code>static<\/code> folder in a Gatsby project:<\/p>\n<ul>\n<li>Referencing a library outside the bundled code to prevent webpack compatibility issues or a lack of specific loaders.<\/li>\n<li>Referencing assets with a specific name, for example, in a web manifest file.<\/li>\n<\/ul>\n<p>You can find a <a href=\"https:\/\/www.gatsbyjs.com\/docs\/how-to\/images-and-media\/static-folder\/\">detailed explanation of the <code>static<\/code> folder and use it to your advantage<\/a> in the Gatsby documentation.<\/p>\n<h3 id=\"embedding-files-from-third-party-services\">Embedding Files From Third-Party Services<\/h3>\n<p>Secondly, you can never be too cautious when embedding third-party services on a website. Replaced content elements, like <code><iframe><\/code>, can introduce various security vulnerabilities, particularly when you do not have control of the source content. By integrating a third party\u2019s scripts, widgets, or content, a website or app is prone to potential vulnerabilities, such as iframe injection or cross-frame scripting.<\/p>\n<p>Moreover, if an integrated third-party service experiences downtime or performance issues, it can directly impact the user experience.<\/p>\n<h2 id=\"conclusion\">Conclusion<\/h2>\n<p>This article explored various approaches for working around common headaches you may encounter when working with Markdown, PDF, and 3D model files in a Gatsby project. In the process, we leveraged several React plugins and Gatsby features that handle how content is parsed, embed files on a page, and manage 3D scenes.<\/p>\n<p>This is also the second article in a brief two-part series that addresses common headaches working with a variety of media types in Gatsby. The <a href=\"https:\/\/www.smashingmagazine.com\/2023\/10\/gatsby-headaches-working-media-part1\/\">first part covers more common media files<\/a>, including images, video, and audio.<\/p>\n<p>If you\u2019re looking for more cures to Gatsby headaches, please <a href=\"https:\/\/www.smashingmagazine.com\/2023\/06\/gatsby-headaches-i18n-part-1\/\">check out my other two-part series<\/a> that investigates internationalization.<\/p>\n<h2 id=\"see-also\">See Also<\/h2>\n<ul>\n<li>\u201c<a href=\"https:\/\/www.smashingmagazine.com\/2023\/06\/gatsby-headaches-i18n-part-1\/\">Gatsby Headaches And How To Cure Them: i18n (Part 1)<\/a>\u201d<\/li>\n<li>\u201c<a href=\"https:\/\/www.smashingmagazine.com\/2023\/06\/gatsby-headaches-i18n-part-2\/\">Gatsby Headaches And How To Cure Them: i18n (Part 2)<\/a>\u201d<\/li>\n<li>\u201c<a href=\"https:\/\/www.smashingmagazine.com\/2023\/10\/gatsby-headaches-working-media-part1\/\">Gatsby Headaches And How To Cure Them: Media Files (Part 1)<\/a>\u201d<\/li>\n<\/ul>\n<div class=\"signature\">\n <img decoding=\"async\" src=\"https:\/\/www.smashingmagazine.com\/images\/logo\/logo--red.png\" alt=\"Smashing Editorial\" width=\"35\" height=\"46\" loading=\"lazy\" \/><br \/>\n <span>(gg, yk, il)<\/span>\n<\/div>\n<\/article>\n","protected":false},"excerpt":{"rendered":"<p>Gatsby Headaches: Working With Media (Part 2) Gatsby Headaches: Working With Media (Part 2) Juan Diego Rodr\u00edguez 2023-10-16T13:00:00+00:00 2024-08-30T10:05:08+00:00 Gatsby is a true Jamstack framework. It works with React-powered components that consume APIs before optimizing and bundling everything to serve as static files with bits of reactivity. That includes media files, like images, video, and […]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[10],"tags":[],"class_list":["post-511","post","type-post","status-publish","format-standard","hentry","category-performance"],"_links":{"self":[{"href":"https:\/\/upprofits.net\/index.php\/wp-json\/wp\/v2\/posts\/511"}],"collection":[{"href":"https:\/\/upprofits.net\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/upprofits.net\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/upprofits.net\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/upprofits.net\/index.php\/wp-json\/wp\/v2\/comments?post=511"}],"version-history":[{"count":1,"href":"https:\/\/upprofits.net\/index.php\/wp-json\/wp\/v2\/posts\/511\/revisions"}],"predecessor-version":[{"id":512,"href":"https:\/\/upprofits.net\/index.php\/wp-json\/wp\/v2\/posts\/511\/revisions\/512"}],"wp:attachment":[{"href":"https:\/\/upprofits.net\/index.php\/wp-json\/wp\/v2\/media?parent=511"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/upprofits.net\/index.php\/wp-json\/wp\/v2\/categories?post=511"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/upprofits.net\/index.php\/wp-json\/wp\/v2\/tags?post=511"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}