├── .github ├── ISSUE_TEMPLATE │ ├── bug.yaml │ ├── feature.yaml │ └── task.yaml ├── pull_request_template.md └── workflows │ ├── template-only-cd.yml │ ├── template-only-docs.yml │ └── template-only-markdownlint-config.json ├── .gitignore ├── CODEOWNERS ├── LICENSE.md ├── README.md ├── copier.yml ├── renovate.json ├── template-only-bin └── lint-markdown.sh ├── template-only-docs ├── decisions │ └── 0001-use-renovate-for-dependency-updates.md ├── set-up-cd.md └── set-up-dependency-management.md └── template ├── .github └── workflows │ ├── cd-{{app_name}}-storybook.yml.jinja │ └── ci-{{app_name}}.yml.jinja ├── .template-application-nextjs └── {{_copier_conf.answers_file}}.jinja ├── docs ├── decisions │ ├── template.md │ └── {{app_name}} │ │ ├── 0000-use-markdown-architectural-decision-records.md │ │ ├── 0001-use-npm.md │ │ ├── 0003-design-system.md │ │ ├── 0004-uswds-in-react.md │ │ ├── 0005-server-rendering.md │ │ ├── 0006-use-nextjs.md │ │ ├── 0007-i18n-type-safety.md │ │ └── 0008-app-router.md └── {{app_name}} │ ├── image-optimization.md │ ├── internationalization.md │ └── security.md └── {{app_name}} ├── .dockerignore ├── .env.development ├── .env.jinja ├── .eslintrc.js ├── .gitignore ├── .grype.yml ├── .prettierignore ├── .prettierrc.js ├── .storybook ├── I18nStoryWrapper.tsx ├── main.mjs ├── manager-head.html └── preview.tsx ├── Dockerfile ├── Makefile ├── Makefile.config.jinja ├── README.md.jinja ├── docker-compose.yml.jinja ├── jest.config.js ├── next-env.d.ts ├── next.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── public └── img │ └── logo.svg ├── scripts ├── postinstall.js └── sassOptions.js ├── src ├── app │ ├── [locale] │ │ ├── health │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ ├── page.stories.tsx │ │ ├── page.test.tsx │ │ ├── page.tsx │ │ └── view.tsx │ └── api │ │ └── hello │ │ └── route.ts ├── components │ ├── Footer.tsx │ ├── Header.test.tsx │ ├── Header.tsx │ ├── Layout.stories.tsx │ ├── Layout.test.tsx │ └── Layout.tsx ├── i18n │ ├── config.ts │ ├── getMessagesWithFallbacks.ts │ ├── messages │ │ ├── en-US │ │ │ └── index.ts │ │ └── es-US │ │ │ └── index.ts │ └── server.ts ├── middleware.ts ├── styles │ ├── _uswds-theme-custom-styles.scss │ ├── _uswds-theme.scss │ └── styles.scss └── types │ └── i18n.d.ts ├── stories └── typography.stories.tsx ├── tests ├── jest.setup.ts └── react-utils.tsx └── tsconfig.json /.github/ISSUE_TEMPLATE/bug.yaml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: File a bug report 3 | labels: ["bug", "triage"] 4 | projects: ["navapbc/4"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thanks for taking the time to fill out this bug report! 10 | - type: textarea 11 | attributes: 12 | label: What happened? 13 | description: Also tell us, what did you expect to happen? 14 | placeholder: Tell us what you see! 15 | validations: 16 | required: true 17 | - type: textarea 18 | attributes: 19 | label: Steps to reproduce 20 | description: Share the steps or a link to a repository we can use to reproduce the problem. 21 | - type: dropdown 22 | id: browsers 23 | attributes: 24 | label: What browsers are you seeing the problem on? 25 | multiple: true 26 | options: 27 | - Not applicable 28 | - Firefox 29 | - Chrome 30 | - Safari 31 | - Microsoft Edge 32 | - type: textarea 33 | id: logs 34 | attributes: 35 | label: Relevant log output 36 | description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. 37 | render: shell 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature.yaml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest an idea for this project 3 | labels: ["triage", "feature"] 4 | projects: ["navapbc/4"] 5 | body: 6 | - type: textarea 7 | attributes: 8 | label: Describe the problem and the solution you'd like 9 | description: A clear and concise description of what the problem is and what you want to happen. 10 | value: | 11 | **Is your feature request related to a problem? Please describe.** 12 | 13 | 14 | **Describe the solution you'd like** 15 | 16 | 17 | **Describe alternatives you've considered** 18 | 19 | - type: textarea 20 | attributes: 21 | label: Additional context 22 | description: Add any other context or screenshots about the feature request here. 23 | - type: dropdown 24 | attributes: 25 | label: Priority 26 | description: How impactful would this be for your project? 27 | multiple: false 28 | options: 29 | - "My project needs this now" 30 | - "I anticipate needing this soon" 31 | - "Nice to have" 32 | - "Just want to discuss" 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/task.yaml: -------------------------------------------------------------------------------- 1 | name: Task 2 | description: Create a task for the team 3 | projects: ["navapbc/4"] 4 | body: 5 | - type: textarea 6 | attributes: 7 | label: What's the task? 8 | validations: 9 | required: true 10 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Ticket 2 | 3 | Resolves #{TICKET NUMBER OR URL} 4 | 5 | ## Changes 6 | 7 | 8 | 9 | ## Context for reviewers 10 | 11 | 12 | 13 | ## Testing 14 | 15 | 16 | -------------------------------------------------------------------------------- /.github/workflows/template-only-cd.yml: -------------------------------------------------------------------------------- 1 | name: Template Deploy 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | workflow_dispatch: 8 | 9 | # Only allow one workflow at a time to prevent race conditions when pushing changes to the project repo 10 | concurrency: template-only-cd 11 | 12 | jobs: 13 | deploy: 14 | name: Deploy 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout template repo 18 | uses: actions/checkout@v4 19 | with: 20 | path: template-application-nextjs 21 | 22 | - name: Checkout project repo 23 | uses: actions/checkout@v4 24 | with: 25 | path: project-repo 26 | repository: navapbc/platform-test-nextjs 27 | token: ${{ secrets.PLATFORM_BOT_GITHUB_TOKEN }} 28 | 29 | - name: Set up Python 30 | uses: actions/setup-python@v5 31 | with: 32 | python-version: '3.13' 33 | 34 | - name: Install nava-platform CLI 35 | run: pipx install --python "$(which python)" git+https://github.com/navapbc/platform-cli 36 | 37 | - name: Configure git 38 | working-directory: project-repo 39 | run: | 40 | git config user.name nava-platform-bot 41 | git config user.email platform-admins@navapbc.com 42 | 43 | - name: Update application template 44 | working-directory: project-repo 45 | run: nava-platform app update --template-uri ${{ github.server_url }}/${{ github.repository }} --version HEAD . app 46 | 47 | - name: Push changes to project repo 48 | working-directory: project-repo 49 | run: git push 50 | -------------------------------------------------------------------------------- /.github/workflows/template-only-docs.yml: -------------------------------------------------------------------------------- 1 | name: Template Documentation Checks 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | lint-markdown: 11 | name: Lint markdown 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | # This is the GitHub Actions-friendly port of the linter used in the Makefile. 16 | - uses: gaurav-nelson/github-action-markdown-link-check@1.0.15 17 | with: 18 | use-quiet-mode: "yes" # errors only. 19 | config-file: ".github/workflows/template-only-markdownlint-config.json" 20 | -------------------------------------------------------------------------------- /.github/workflows/template-only-markdownlint-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignorePatterns": [ 3 | { 4 | "pattern": "0005-example.md" 5 | }, 6 | { 7 | "pattern": "localhost" 8 | }, 9 | { 10 | "pattern": "127.0.0.1" 11 | }, 12 | { 13 | "pattern": "https://www.figma.com/community/file/836611771720754351" 14 | } 15 | ], 16 | "replacementPatterns": [ 17 | { 18 | "pattern": "^/", 19 | "replacement": "{{BASEURL}}/" 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # MacOS files 2 | .DS_Store 3 | # Developer-specific IDE settings 4 | .vscode 5 | .env.local -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | @navapbc/platform-frontend -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Template Next.js React application 2 | 3 | This is a template repository for a React web application using the Next.js framework. 4 | 5 | See [`navapbc/platform`](https://github.com/navapbc/platform) for other template repos. 6 | 7 | ## Features 8 | 9 | - Framework for server-side rendered, static, or hybrid React applications 10 | - TypeScript and React testing tools 11 | - U.S. Web Design System for themeable styling and a set of common components 12 | - Type checking, linting, and code formatting tools 13 | - Storybook for a frontend workshop environment 14 | 15 | ## Repo structure 16 | 17 | ```text 18 | . 19 | ├── template # The template (the things that get installed/updated) 20 | │ ├── .github # GitHub workflows 21 | │ ├── docs # Project docs and decision records 22 | │ └── {{app_name}} # Application code 23 | ├── template-only-bin # Template repo scripts 24 | └── template-only-docs # Template repo docs 25 | ``` 26 | 27 | ## Installation 28 | 29 | To get started using the template application on your project: 30 | 31 | 1. [Install the nava-platform tool](https://github.com/navapbc/platform-cli). 32 | 2. Install template by running in your project's root: 33 | ```sh 34 | nava-platform app install --template-uri https://github.com/navapbc/template-application-nextjs . 35 | ``` 36 | 3. Follow the steps in `//README.md` to set up the application locally. 37 | 4. Optional, if using the Platform infra template: [Follow the steps in the `template-infra` README](https://github.com/navapbc/template-infra#installation) to set up the various pieces of your infrastructure. 38 | 39 | ## Learn more 40 | 41 | - [Dependency management](./template-only-docs/set-up-dependency-management.md) 42 | - [Deployment](./template-only-docs/set-up-cd.md) 43 | -------------------------------------------------------------------------------- /copier.yml: -------------------------------------------------------------------------------- 1 | # 2 | # App vars 3 | # 4 | app_name: 5 | type: str 6 | help: The name of the app 7 | validator: >- 8 | {% if not (app_name | regex_search('^[a-z0-9\-_]+$')) %} 9 | The app name can not be empty and should only contain lower case letters, digits, dashes, and underscores. 10 | {% endif %} 11 | 12 | app_local_port: 13 | type: int 14 | help: "The port to be used in local development of '{{ app_name }}'" 15 | default: 3000 16 | 17 | app_enable_storybook_cd: 18 | type: bool 19 | default: false 20 | 21 | _envops: 22 | trim_blocks: true 23 | lstrip_blocks: true 24 | 25 | # ideally we could just: 26 | # 27 | # _answers_file: .template-application-nextjs/{{app_name}}.yml 28 | # 29 | # but alas, no: 30 | # 31 | # https://github.com/copier-org/copier/issues/1868 32 | 33 | _subdirectory: template 34 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base", 5 | "group:allNonMajor", 6 | "schedule:monthly", 7 | "docker:enableMajor" 8 | ], 9 | "timezone": "America/Los_Angeles", 10 | "enabledManagers": ["dockerfile", "npm"], 11 | "rangeStrategy": "update-lockfile", 12 | "ignoreUnstable": true, 13 | "labels": ["dependencies"], 14 | "packageRules": [ 15 | { 16 | "description": ["Group React packages together"], 17 | "matchPackagePatterns": [ 18 | "react", 19 | "@types/react", 20 | "react-dom", 21 | "@types/react-dom" 22 | ], 23 | "groupName": "React" 24 | }, 25 | { 26 | "description": ["Group Storybook packages together"], 27 | "matchPackagePatterns": ["storybook"], 28 | "groupName": "Storybook" 29 | }, 30 | { 31 | "description": ["Group test packages together"], 32 | "matchPackagePatterns": ["jest", "testing-library"], 33 | "groupName": "Jest" 34 | }, 35 | { 36 | "description": ["Group CSS-related packages together"], 37 | "matchPackagePatterns": ["postcss", "sass"], 38 | "groupName": "Styling" 39 | }, 40 | { 41 | "description": ["Group USWDS packages together"], 42 | "matchPackagePatterns": ["uswds"], 43 | "groupName": "USWDS" 44 | }, 45 | { 46 | "description": [ 47 | "Prettier v3 breaks the VS Code Prettier extension, last time it was tested" 48 | ], 49 | "enabled": false, 50 | "matchPackagePatterns": ["^prettier"], 51 | "groupName": "Prettier" 52 | }, 53 | { 54 | "description": ["Leave peer dependencies alone"], 55 | "enabled": false, 56 | "matchDepTypes": ["peerDependencies"] 57 | } 58 | ], 59 | "ignoreDeps": [], 60 | "assignees": [], 61 | "reviewers": [] 62 | } 63 | -------------------------------------------------------------------------------- /template-only-bin/lint-markdown.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # To make things simpler, ensure we're in the repo's root directory (one directory up) before 4 | # running, regardless where the user is when invoking this script. 5 | 6 | # Grab the full directory name for where this script lives. 7 | SCRIPT_DIR=$(readlink -f "$0" | xargs dirname) 8 | 9 | # Move up to the root since we want to do everything relative to that. Note that this only impacts 10 | # this script, but will leave the user wherever they were when the script exists. 11 | cd "${SCRIPT_DIR}/.." >/dev/null || exit 1 12 | 13 | LINK_CHECK_CONFIG=".github/workflows/template-only-markdownlint-config.json" 14 | 15 | # Recursively find all markdown files (*.md) in the current directory, excluding node_modules and .venv subfolders. 16 | # Pass them in as args to the lint command using the handy `xargs` command. 17 | find . -name \*.md -not -path "*/node_modules/*" -not -path "*/.venv/*" -print0 | xargs -0 -n1 npx markdown-link-check --config $LINK_CHECK_CONFIG 18 | -------------------------------------------------------------------------------- /template-only-docs/decisions/0001-use-renovate-for-dependency-updates.md: -------------------------------------------------------------------------------- 1 | # Use Renovate over Dependabot for Dependency Updates 2 | 3 | * Deciders: @aligg, @sawyerh, @lorenyu 4 | * Date: 2022-10 5 | 6 | 7 | ## Context and Problem Statement 8 | Initially this template repo used dependabot for dependency management. We moved to renovate to try and reduce noise and increase our ability to customize how we do upgrades while continuing to update our dependencies in a timely manner. We specifically thought it would be useful to try out renovate in this template directory to see if it's something that could add value across other projects at Nava. 9 | 10 | Some Renovate benefits: 11 | * Renovate is free and open source, and usage is increasing. 12 | * Batching dependency update PRs. For example, you can batch all minor updates together on a monthly basis. You can also batch particular dependencies - like all jest or all react together in one PRs. 13 | * Granular scheduling. For example, you can opt to only receive upgrade PRs once a month for minor updates, and security related upgrades right away. 14 | * Granular automerging. For example, you can set all minor upgrades that pass tests to automerge, and any major updates go for human review. 15 | * Auto-assign reviewers to specific people or code owner groups to increase ownership. 16 | * Renovate also comes with some dashboard functionality, mostly unexplored for now: https://docs.renovatebot.com/key-concepts/dashboard/ 17 | 18 | Some Renovate drawbacks: 19 | * Supports (I think) fewer languages than dependabot. Renovate currently supports Java, JavaScript, Python, Ruby, PHP, GoLang and a few addl languages [See language support section](https://docs.renovatebot.com/) 20 | 21 | ## Considered Options 22 | We mostly considered the merits of renovate and dependabot when making this decision. Other players in this space include snyk, which as I understand covers more than our specific use-case. 23 | 24 | ## Decision Outcome 25 | Since it is low risk to try out (straight-forward to implement, easy to reverse) renovate we decided to go for it. 26 | 27 | ## Links - Resources 28 | * [Renovate Docs](https://docs.renovatebot.com/) 29 | * [Renovate Tutorial](https://github.com/renovatebot/tutorial) 30 | 31 | ## Links - Some Renovate Config File Examples 32 | * [freeCodeCamp](https://github.com/freeCodeCamp/freeCodeCamp/blob/22bb60196ab63160a5e4242da6eca31536d380e1/renovate.json) 33 | * [Prisma](https://github.com/prisma/prisma/blob/6ec631c27446baed0a65d8ddc01cb26fab776572/.github/renovate.json) 34 | 35 | ## Some common renovate config flags explained 36 | - Only target npm packages: `"enabledManagers":["npm"]` 37 | - Renovate favors pinning dependencies [reference](https://docs.renovatebot.com/dependency-pinning/). We can disable pinning with `"rangeStrategy":"replace"` 38 | - We can pull in upgrades only after they've been in the wild for a few days using: `"npm": {"stabilityDays": 2}` 39 | - Renovate offers quite a few presets (we're pulling in their base config to start). You can read through presets [here](https://docs.renovatebot.com/presets-config/) 40 | 41 | ## Renovate <> Dependabot Configuration 42 | Note that for renovate to read vulnerability alerts, it needs access to dependency graph and dependabot alerts. More details [here](https://docs.renovatebot.com/configuration-options/#vulnerabilityalerts). -------------------------------------------------------------------------------- /template-only-docs/set-up-cd.md: -------------------------------------------------------------------------------- 1 | # Deployments 2 | 3 | ## Next.js app 4 | 5 | The Next.js app can be deployed as a static HTML export or as a Node.js server, depending on your needs. By default, the Next.js and Infra templates are configured to deploy the app as a Node.js server in a Docker container. Reference the server rendering ADR for additional context (`/template/docs/decisions/{{app_name}}/0005-server-rendering.md`). 6 | 7 | The [Next.js deployment documentation](https://nextjs.org/docs/deployment) provides more information on the various ways to deploy a Next.js application. 8 | 9 | ## Storybook 10 | 11 | Storybook can be deployed as a static HTML export. This template includes a GitHub workflow that will build and deploy Storybook to [GitHub Pages](https://pages.github.com/). To set up this workflow: 12 | 13 | 14 | 1. You can enable continuous deployment of Storybook to GitHub Pages by running: 15 | ```shell 16 | nava-platform app update --data app_enable_storybook_cd=true . 17 | ``` 18 | 1. Select "GitHub Actions" from the **Source** dropdown field on the "Pages" tab in your repo settings (`Settings > Pages`). 19 | 1. Trigger the workflow from the repo's Actions tab or by pushing a commit to your main branch 20 | 21 | If your GitHub repo is public and you're using the default domain, Storybook will be hosted at a subdirectory: `https://.github.io//`. A `NEXT_PUBLIC_BASE_PATH` environment variable is available to your code so that any relative paths can be properly prefixed with the subdirectory where your Storybook is hosted. The Storybook configuration is already setup to use this environment variable, as long as the `basePath` in your `next.config.js` uses `process.env.NEXT_PUBLIC_BASE_PATH` as the value. 22 | 23 | ### Alternatives 24 | 25 | One constraint of GitHub Pages is you only have one environment. The [Storybook deployment documentation](https://storybook.js.org/docs/react/sharing/publish-storybook) provides more information on other deployment options. 26 | -------------------------------------------------------------------------------- /template-only-docs/set-up-dependency-management.md: -------------------------------------------------------------------------------- 1 | # Dependency Management with Renovate 2 | 3 | Out of the box this repo uses [Renovate](https://docs.renovatebot.com/) for dependency management. More information on the decision to try renovate can be found [here](../template-only-docs/decisions/0001-use-renovate-for-dependency-updates.md). Renovate is free and open-source and allows us to bundle dependency updates together and customize their scheduling. 4 | 5 | **Opting out of renovate**: 6 | 7 | TODO: renovate file is not installed by old scripts?? 8 | 9 | If you decide you don't want to use renovate, you can delete the `renovate.json` file from the template code. If you plan to rely on [Dependabot](https://docs.github.com/en/code-security/dependabot), you'll likely want to add a `.github/dependabot.yml` file ([example here](https://github.com/navapbc/template-application-nextjs/blob/7ddb06b23524536db2e24bd43ec3ff7ec19d52bf/.github/dependabot.yml)) 10 | 11 | **Getting started with renovate**: 12 | 13 | 1. Install Renovate's GitHub App for your repo ([Docs](https://docs.renovatebot.com/getting-started/installing-onboarding/#hosted-githubcom-app)). For most projects, you most likely only want to do this for your select repository. Note that if you prefer not to use the GitHub App, renovate does offer some alternatives including self-hosting. 14 | 2. After installation, Renovate should open a "Configure Renovate" pull request in your repository. Once you merge this PR, you will be all set. For more detail on this onboarding PR (what it contains, what to look for, what you can customize etc), check out [renovate's onboarding tutorial README for useful examples](https://github.com/renovatebot/tutorial). Note that this codebase comes with a `renovate.json` file out of the box with configuration options you can keep dependending on your preferences. 15 | 3. Note that Renovate can read GitHub's Vulnerability Alerts to customize pull requests. For this to work, you must enable Dependency graph and Dependabot alerts (This is covered in the 'other Github settings' section above, but mentioning here explicitly). For more detail on vulnerability management with renovate, [see here](https://github.com/renovatebot/renovate/blob/main/docs/usage/configuration-options.md#vulnerabilityalerts). 16 | 17 | **Beyond the basics**: 18 | 19 | After following the above steps your repository should be good to go with Renovate in terms of the basics. Future optimizations you may wish to look into include the [Renovate Dashboard](https://docs.renovatebot.com/key-concepts/dashboard/) and adding the renovate-config-validator program to validate any future renovate config changes prior to merge ([documentation here](https://docs.renovatebot.com/getting-started/installing-onboarding/#reconfigure-via-pr)). 20 | -------------------------------------------------------------------------------- /template/.github/workflows/cd-{{app_name}}-storybook.yml.jinja: -------------------------------------------------------------------------------- 1 | name: Deploy Storybook 2 | 3 | on: 4 | {% if app_enable_storybook_cd %} 5 | push: 6 | branches: ["main"] 7 | paths: 8 | - {{ app_name }}/** 9 | {% else %} 10 | # !! When you are ready to enable Storybook CD, run: 11 | # 12 | # nava-platform app update --data app_enable_storybook_cd=true . 13 | # 14 | # to enable these lines. They are here as comments for context. 15 | # 16 | # push: 17 | # branches: ["main"] 18 | # paths: 19 | # - {{ app_name }}/** 20 | {% endif %} 21 | 22 | # Allows you to run this workflow manually from the Actions tab 23 | workflow_dispatch: 24 | 25 | # Sets permissions of the GITHUB_TOKEN to allow access to GitHub Pages 26 | permissions: 27 | contents: read 28 | pages: write 29 | id-token: write 30 | 31 | # Cancel any older in-progress runs of this workflow 32 | concurrency: 33 | group: "pages" 34 | cancel-in-progress: true 35 | 36 | jobs: 37 | build: 38 | runs-on: ubuntu-latest 39 | steps: 40 | - name: Checkout 41 | uses: actions/checkout@v4 42 | - name: Setup Node 43 | uses: actions/setup-node@v4 44 | with: 45 | node-version-file: ./{{ app_name }}/package.json 46 | cache-dependency-path: ./{{ app_name }}/package-lock.json # or yarn.lock 47 | cache: npm # or yarn 48 | - name: Setup Pages 49 | uses: actions/configure-pages@v5 50 | id: pages_config 51 | - name: Install dependencies 52 | run: npm ci 53 | working-directory: ./{{ app_name }} 54 | - name: Build 55 | run: NEXT_PUBLIC_BASE_PATH=${{'{{'}} steps.pages_config.outputs.base_path {{'}}'}} npm run storybook-build 56 | working-directory: ./{{ app_name }} 57 | - name: Upload artifact 58 | uses: actions/upload-pages-artifact@v3 59 | with: 60 | path: ./{{ app_name }}/storybook-static 61 | 62 | deploy: 63 | environment: 64 | name: github-pages 65 | url: ${{'{{'}} steps.hosting.outputs.page_url {{'}}'}} 66 | runs-on: ubuntu-latest 67 | needs: build 68 | steps: 69 | - name: Deploy to GitHub Pages 70 | id: hosting 71 | uses: actions/deploy-pages@v4 72 | -------------------------------------------------------------------------------- /template/.github/workflows/ci-{{app_name}}.yml.jinja: -------------------------------------------------------------------------------- 1 | name: CI - {{ app_name }} 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | paths: 9 | - {{ app_name }}/** 10 | - .github/workflows/ci-{{ app_name }}.yml 11 | 12 | defaults: 13 | run: 14 | working-directory: ./{{ app_name }} 15 | 16 | env: 17 | NODE_VERSION_FILE: ./{{ app_name }}/package.json 18 | LOCKFILE_PATH: ./{{ app_name }}/package-lock.json # or yarn.lock 19 | PACKAGE_MANAGER: npm # or yarn 20 | 21 | concurrency: 22 | group: ${{'{{'}} github.workflow {{'}}'}}-${{'{{'}} github.ref {{'}}'}} 23 | cancel-in-progress: true 24 | 25 | jobs: 26 | tests: 27 | name: Tests 28 | runs-on: ubuntu-latest 29 | 30 | steps: 31 | - uses: actions/checkout@v4 32 | - uses: actions/setup-node@v4 33 | with: 34 | node-version-file: ${{'{{'}} env.NODE_VERSION_FILE {{'}}'}} 35 | cache-dependency-path: ${{'{{'}} env.LOCKFILE_PATH {{'}}'}} 36 | cache: ${{'{{'}} env.PACKAGE_MANAGER {{'}}'}} 37 | - run: npm ci 38 | - run: npm run test -- --testLocationInResults --json --outputFile=coverage/report.json 39 | - uses: ArtiomTr/jest-coverage-report-action@v2 40 | with: 41 | coverage-file: coverage/report.json 42 | test-script: npm test 43 | working-directory: {{ app_name }} 44 | annotations: failed-tests 45 | # base-coverage-file: report.json 46 | 47 | lint: 48 | name: Lint 49 | runs-on: ubuntu-latest 50 | 51 | steps: 52 | - uses: actions/checkout@v4 53 | - uses: actions/setup-node@v4 54 | with: 55 | node-version-file: ${{'{{'}} env.NODE_VERSION_FILE {{'}}'}} 56 | cache-dependency-path: ${{'{{'}} env.LOCKFILE_PATH {{'}}'}} 57 | cache: ${{'{{'}} env.PACKAGE_MANAGER {{'}}'}} 58 | - run: npm ci 59 | - run: npm run lint 60 | 61 | types: 62 | name: Type check 63 | runs-on: ubuntu-latest 64 | 65 | steps: 66 | - uses: actions/checkout@v4 67 | - uses: actions/setup-node@v4 68 | with: 69 | node-version-file: ${{'{{'}} env.NODE_VERSION_FILE {{'}}'}} 70 | cache-dependency-path: ${{'{{'}} env.LOCKFILE_PATH {{'}}'}} 71 | cache: ${{'{{'}} env.PACKAGE_MANAGER {{'}}'}} 72 | - run: npm ci 73 | - run: npm run ts:check 74 | 75 | formatting: 76 | name: Format check 77 | runs-on: ubuntu-latest 78 | 79 | steps: 80 | - uses: actions/checkout@v4 81 | - uses: actions/setup-node@v4 82 | with: 83 | node-version-file: ${{'{{'}} env.NODE_VERSION_FILE {{'}}'}} 84 | cache-dependency-path: ${{'{{'}} env.LOCKFILE_PATH {{'}}'}} 85 | cache: ${{'{{'}} env.PACKAGE_MANAGER {{'}}'}} 86 | - run: npm ci 87 | - run: npm run format-check 88 | 89 | # Confirms the app still builds successfully 90 | check-app-builds: 91 | name: Build check - App 92 | runs-on: ubuntu-latest 93 | 94 | steps: 95 | - uses: actions/checkout@v4 96 | - uses: actions/setup-node@v4 97 | with: 98 | node-version-file: ${{'{{'}} env.NODE_VERSION_FILE {{'}}'}} 99 | cache-dependency-path: ${{'{{'}} env.LOCKFILE_PATH {{'}}'}} 100 | cache: ${{'{{'}} env.PACKAGE_MANAGER {{'}}'}} 101 | 102 | # https://nextjs.org/docs/advanced-features/ci-build-caching 103 | - uses: actions/cache@v4 104 | with: 105 | path: | 106 | ~/.npm 107 | ${{'{{'}} github.workspace {{'}}'}}/{{ app_name }}/.next/cache 108 | # Generate a new cache whenever packages or source files change. 109 | key: ${{'{{'}} runner.os {{'}}'}}-nextjs-${{'{{'}} hashFiles('**/package-lock.json') {{'}}'}}-${{'{{'}} hashFiles('**.[jt]s', '**.[jt]sx') {{'}}'}} 110 | # If source files changed but packages didn't, rebuild from a prior cache. 111 | restore-keys: | 112 | ${{'{{'}} runner.os {{'}}'}}-nextjs-${{'{{'}} hashFiles('**/package-lock.json') {{'}}'}}- 113 | 114 | - run: npm ci 115 | - run: npm run build -- --no-lint 116 | 117 | # Confirms Storybook still builds successfully 118 | check-storybook-builds: 119 | name: Build check - Storybook 120 | runs-on: ubuntu-latest 121 | 122 | steps: 123 | - uses: actions/checkout@v4 124 | - uses: actions/setup-node@v4 125 | with: 126 | node-version-file: ${{'{{'}} env.NODE_VERSION_FILE {{'}}'}} 127 | cache-dependency-path: ${{'{{'}} env.LOCKFILE_PATH {{'}}'}} 128 | cache: ${{'{{'}} env.PACKAGE_MANAGER {{'}}'}} 129 | - run: npm ci 130 | - run: npm run storybook-build 131 | -------------------------------------------------------------------------------- /template/.template-application-nextjs/{{_copier_conf.answers_file}}.jinja: -------------------------------------------------------------------------------- 1 | # Changes here will be overwritten by Copier 2 | {{ _copier_answers|to_nice_yaml -}} 3 | -------------------------------------------------------------------------------- /template/docs/decisions/template.md: -------------------------------------------------------------------------------- 1 | # [short title of solved problem and solution] 2 | 3 | * Status: [proposed | rejected | accepted | deprecated | … | superseded by [ADR-0005](0005-example.md)] 4 | * Deciders: [list everyone involved in the decision] 5 | * Date: [YYYY-MM-DD when the decision was last updated] 6 | 7 | Technical Story: [description | ticket/issue URL] 8 | 9 | ## Context and Problem Statement 10 | 11 | [Describe the context and problem statement, e.g., in free form using two to three sentences. You may want to articulate the problem in the form of a question.] 12 | 13 | ## Decision Drivers 14 | 15 | * [driver 1, e.g., a force, facing concern, …] 16 | * [driver 2, e.g., a force, facing concern, …] 17 | * … 18 | 19 | ## Considered Options 20 | 21 | * [option 1] 22 | * [option 2] 23 | * [option 3] 24 | * … 25 | 26 | ## Decision Outcome 27 | 28 | Chosen option: "[option 1]", because [justification. e.g., only option, which meets k.o. criterion decision driver | which resolves force force | … | comes out best (see below)]. 29 | 30 | ### Positive Consequences 31 | 32 | * [e.g., improvement of quality attribute satisfaction, follow-up decisions required, …] 33 | * … 34 | 35 | ### Negative Consequences 36 | 37 | * [e.g., compromising quality attribute, follow-up decisions required, …] 38 | * … 39 | 40 | ## Pros and Cons of the Options 41 | 42 | ### [option 1] 43 | 44 | [example | description | pointer to more information | …] 45 | 46 | * Good, because [argument a] 47 | * Good, because [argument b] 48 | * Bad, because [argument c] 49 | * … 50 | 51 | ### [option 2] 52 | 53 | [example | description | pointer to more information | …] 54 | 55 | * Good, because [argument a] 56 | * Good, because [argument b] 57 | * Bad, because [argument c] 58 | * … 59 | 60 | ### [option 3] 61 | 62 | [example | description | pointer to more information | …] 63 | 64 | * Good, because [argument a] 65 | * Good, because [argument b] 66 | * Bad, because [argument c] 67 | * … 68 | 69 | ## Links 70 | 71 | * [Link type] [Link to ADR] 72 | * … 73 | -------------------------------------------------------------------------------- /template/docs/decisions/{{app_name}}/0000-use-markdown-architectural-decision-records.md: -------------------------------------------------------------------------------- 1 | # Use Markdown Architectural Decision Records 2 | 3 | ## Context and Problem Statement 4 | 5 | We want to record architectural decisions made in this project. 6 | Which format and structure should these records follow? 7 | 8 | ## Considered Options 9 | 10 | * [MADR](https://adr.github.io/madr/) 2.1.2 – The Markdown Architectural Decision Records 11 | * [Michael Nygard's template](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions) – The first incarnation of the term "ADR" 12 | * [Sustainable Architectural Decisions](https://www.infoq.com/articles/sustainable-architectural-design-decisions) – The Y-Statements 13 | * Other templates listed at 14 | * Formless – No conventions for file format and structure 15 | 16 | ## Decision Outcome 17 | 18 | Chosen option: "MADR 2.1.2", because 19 | 20 | * Implicit assumptions should be made explicit. 21 | Design documentation is important to enable people to understand the decisions later on. 22 | See also [A rational design process: How and why to fake it](https://doi.org/10.1109/TSE.1986.6312940). 23 | * The MADR format is lean and fits our development style. 24 | * The MADR structure is comprehensible and facilitates usage & maintenance. 25 | * The MADR project is vivid. 26 | * Version 2.1.2 is the latest one available when starting to document ADRs. 27 | -------------------------------------------------------------------------------- /template/docs/decisions/{{app_name}}/0001-use-npm.md: -------------------------------------------------------------------------------- 1 | # Use NPM over Yarn Architectural Decision Records 2 | 3 | * Deciders: @aligg, @sawyerh, @lorenyu 4 | * Date: 2022-09 5 | 6 | 7 | ## Context and Problem Statement 8 | Initially, this template repo used yarn for package management. We moved to npm because: 9 | * npm is pre-bundled with node, so using npm removes an installation step 10 | * some projects work on government-furnished equipment and an additional package installation (e.g. installing yarn) is a significant and time-consuming step 11 | * npm and yarn are comparable in function for the purposes of this template 12 | 13 | 14 | ## Considered Options 15 | We considered the merits of yarn and npm only when making this decision. 16 | 17 | ## Decision Outcome 18 | Chose npm to reduce installations and bureaucratic hurdles for folks using this template out of the box. 19 | 20 | ## Links 21 | * [Original GitHub issue for reference](https://github.com/navapbc/template-application-nextjs/issues/11) 22 | -------------------------------------------------------------------------------- /template/docs/decisions/{{app_name}}/0003-design-system.md: -------------------------------------------------------------------------------- 1 | # Use U.S. Web Design System for components and utility classes 2 | 3 | - Status: Accepted 4 | - Deciders: Loren Yu, Rocket Lee, Sawyer Hollenshead 5 | 6 | ## Context and Problem Statement 7 | 8 | Projects should avoid reinventing the wheel where possible. A common place to do this is in the UI, by using a design system for front-end components and utility classes. This can help avoid inconsistencies in the UI, and can reduce barriers for new developers. 9 | 10 | We want to use a design system that is: 11 | 12 | - Section 508 compliant 13 | - Open source 14 | - Well maintained and documented 15 | - Includes the typical components and design patterns needed for government websites 16 | 17 | ## Considered Options 18 | 19 | - [U.S. Web Design System (USWDS)](https://designsystem.digital.gov/) 20 | - [CMS Design System](https://design.cms.gov/) 21 | 22 | ## Decision Outcome 23 | 24 | The template will provide U.S. Web Design System styling out of the box. 25 | 26 | We will not follow their [install documentation](https://designsystem.digital.gov/documentation/getting-started-for-developers/), which suggests using Gulp as a task runner. Instead, to reduce the number of dependencies and configurations, we'll leverage Next.js's and Storybook's built-in Sass support. Copying the USWDS static assets into the project will be handled by a [`postinstall`](https://docs.npmjs.com/cli/v8/using-npm/scripts) script in `package.json`. 27 | 28 | ### Positive Consequences 29 | 30 | - USWDS is the most popular design system for U.S. government websites and is maintained by GSA employees. It is the recommended way to meet the website standards detailed in the [21st Century Integrated Digital Experience Act](https://digital.gov/resources/21st-century-integrated-digital-experience-act/). [More key benefits can be read about here](https://designsystem.digital.gov/about/key-benefits/). 31 | - [Project teams can theme the USWDS](https://www.navapbc.com/insights/us-web-design-system) if their project needs to match an existing brand. 32 | 33 | ### Negative Consequences 34 | 35 | - Unlike the CMS Design System, USWDS doesn't provide React components. Project teams will need to create their own React components that output USWDS markup, or install a third-party library like [`react-uswds`](https://github.com/trussworks/react-uswds). In the future, [the template could include this library by default](https://github.com/navapbc/template-application-nextjs/issues/19). 36 | - CMS projects may need to swap out USWDS for the CMS Design System, although the CMS Design System is based on USWDS, so this may not be necessary right away. 37 | 38 | ## Links 39 | 40 | - [Previous research was done by Kalvin Wang and Shannon Alexander Navarro related to USWDS React libraries](https://docs.google.com/document/d/1KRWzH_wJUPKkFmBlxj6SM2yN3W7Or89Wa4TBVM3Ksog/edit) 41 | -------------------------------------------------------------------------------- /template/docs/decisions/{{app_name}}/0004-uswds-in-react.md: -------------------------------------------------------------------------------- 1 | # U.S. Web Design System in React 2 | 3 | - Status: Accepted 4 | - Deciders: @sawyerh, @aligg, @lorenyu, @rocketnova 5 | - Date: 2022-12-05 6 | 7 | Technical Story: #19 8 | 9 | ## Context and Problem Statement 10 | 11 | - The U.S. Web Design System (USWDS) only provides HTML and CSS for its components. It includes a small bit of vanilla JS to add interactivity to some components like the date picker. 12 | - It's common for projects to write their own React components to output the USWDS HTML, to reduce the amount of boilerplate needed to use the USWDS components. 13 | - [Previous research by Kalvin and Shannon](https://docs.google.com/document/d/1KRWzH_wJUPKkFmBlxj6SM2yN3W7Or89Wa4TBVM3Ksog/edit) discovered that Nava engineers and designers universally agreed that being able to use a React USWDS component library when starting new projects would be valuable. 14 | 15 | ## Considered Options 16 | 17 | - Use the existing open-source [`react-uswds` library](https://github.com/trussworks/react-uswds) 18 | - Create our own React USWDS component library 19 | - Leave the responsibility to each project team 20 | 21 | ## Decision Outcome 22 | 23 | Add [`react-uswds`](https://github.com/trussworks/react-uswds) as a template dependency, making it available to all teams who use the template. The primary reasons are to avoid reinventing the wheel and because it's overall a well-built and maintained library. 24 | 25 | ## Pros and Cons of the Options 26 | 27 | ### Use the existing open-source [`react-uswds` library](https://github.com/trussworks/react-uswds) 28 | 29 | `react-uswds` is maintained by Truss, another vendor in this space. [A Storybook for it can be found here](https://trussworks.github.io/react-uswds/). Truss also maintains a [USWDS Figma library](https://www.figma.com/community/file/836611771720754351) for designers. 30 | 31 | #### Pros 32 | 33 | - Includes React components for all USWDS components and patterns. 34 | - Fairly well maintained. 35 | - Intentionally does not include any non-USWDS components. 36 | - Supports USWDS v3 (latest version) 37 | - This was the recommended approach coming out of [Kalvin and Shannon's research](https://docs.google.com/document/d/1KRWzH_wJUPKkFmBlxj6SM2yN3W7Or89Wa4TBVM3Ksog/edit). 38 | 39 | #### Cons 40 | 41 | - They [pin the `@uswds/uswds` dependency version](https://github.com/trussworks/react-uswds/blob/a0558b69ec5b99903cfa8edddf2d8b058f5e296c/package.json#L52) to a specific version, which means that a project cannot use a newer version of USWDS until `react-uswds` updates it on their end. In practice, this could mean that a project may have delayed access to new component styles or CSS bug fixes that USWDS releases. 42 | - Not necessarily a con, but just to call it out: We've only done a lightweight review of their technical implementation and hygiene — there's testing and linting, no reported a11y issues are open in GitHub or reported in Storybook, but we haven't done a comprehensive review of their code or a full accessibility audit. We're operating on trust in Truss's technical expertise, and an assumption that the outputted HTML markup is close to identical to what USWDS provides, so any a11y issues would likely be on USWDS's end. 43 | 44 | ### Create our own React USWDS component library 45 | 46 | Nava could create our own React USWDS component library, similar to `react-uswds`. 47 | 48 | #### Pros 49 | 50 | - We'd have full control over the technical approach and wouldn't have a dependency on another vendor to incorporate changes or release new versions. 51 | 52 | #### Cons 53 | 54 | - Requires more time and effort than using an existing library. We'd have to build and maintain the library. 55 | - Reinventing the wheel. We can always fork `react-uswds` if it no longer meets our needs. 56 | 57 | ### Leave the responsibility to each project team 58 | 59 | This is the current approach. Each project team is responsible for creating its own React components for the USWDS components they need. 60 | 61 | #### Pros 62 | 63 | - No additional work is required from the Platform team. 64 | 65 | #### Cons 66 | 67 | - Each project team has to spend time and effort building the components or making technical decisions related to how they'll integrate USWDS. Teams then have to write their own tests and fix their own bugs for these components. Overall a potentially poor use of time and effort. 68 | 69 | ## Links 70 | 71 | - [Decision to use the USWDS](./0003-design-system.md) 72 | - [Kalvin and Shannon's research](https://docs.google.com/document/d/1KRWzH_wJUPKkFmBlxj6SM2yN3W7Or89Wa4TBVM3Ksog/edit) 73 | - [Evaluation of `react-uswds`](https://docs.google.com/document/d/1T3eG4oRofDE_NkfL7-xEqS39ORlrXlI8bFYcjGaYoWs/edit) 74 | -------------------------------------------------------------------------------- /template/docs/decisions/{{app_name}}/0005-server-rendering.md: -------------------------------------------------------------------------------- 1 | ## Context and Problem Statement 2 | 3 | Next.js provides multiple ways of rendering a website, which have different commands: `next start` vs `next export`. For the platform, we need to determine which should be the default. 4 | 5 | ## Considered options 6 | 7 | 1. Server rendering (`next start`): Generates the full HTML markup for a page on the server in response to navigation. Initial page data can be fetched on the server. 8 | 2. Prerendering (`next export`): At compile time, a separate HTML file is generated for each URL. Only the initial state of the page is generated as static HTML. To display personalized data, client-side JS is required and the page's DOM is updated. 9 | 10 | ## Decision drivers 11 | 12 | 1. The selected option should represent what we consider the preferred approach for the types of websites we're typically building with Next.js: authenticated, personalized web applications (claimant portals, compare tools, case management systems, etc). 13 | 2. Reduce the need for third-party dependencies or custom code, when a native option works just as well. 14 | 3. A (reasonable) increase in cloud costs is acceptable if it results in a system that is more maintainable for software teams and the government in the long term. 15 | 4. Prioritize end-user experience above developer experience. 16 | 17 | ## Decision Outcome 18 | 19 | Server rendering is the best option when the web application requires "live" data, such as the personalized sites we often build at Nava, like claimant portals or case management systems. Server rendering requires more upfront effort on the infra side, but it enables teams to achieve a clearer separation of concerns and write less application code in the long run. This can translate to web applications that work well for a large spectrum of device and network conditions. 20 | 21 | If a project team is building a site that renders the same content for every user, they can change their application to utilize [Next.js's static HTML export functionality](https://nextjs.org/docs/advanced-features/static-html-export) (prerendering). This flexibility to do either server rendering or prerendering within the same React framework is one benefit to using Next.js. 22 | 23 | ## Pros and Cons of the Options 24 | 25 | ### Server rendering 26 | 27 | Pros 28 | 29 | - Data fetching occurs on the server. The browser natively handles the page's loading state. This means less overall code to write, test, and maintain. An uncaught error on the server will be louder (in a good way) than an uncaught error on the client (which could result in a never-ending spinner). 30 | - Makes it easier to implement a clearer separation of concerns: 31 | - [Middleware](https://nextjs.org/docs/advanced-features/middleware) provide a place for enforcing auth, reading/setting secure cookies, setting HTTP headers, and redirects. 32 | - [Loaders](https://nextjs.org/docs/basic-features/data-fetching/get-server-side-props) provide a place for fetching all data required for rendering the page. 33 | - Running page logic and rendering on the server makes it possible to send lighter payloads to the client. This approach can work well for a large spectrum of device and network conditions. [You can make your server fast, but you can't control the user's device or network](https://remix.run/docs/en/v1/pages/philosophy%23serverclient-model). 34 | - Data fetching on the server enables accessing authenticated APIs (e.g. using TLS mutual auth to talk to fetch data from a protected third-party API). 35 | - Low effort to implement [dynamic routes](https://nextjs.org/docs/routing/dynamic-routes) (e.g `/claim/:claim_id`) 36 | - [API routes](https://nextjs.org/docs/pages/building-your-application/routing/api-routes) can be created to handle other types of HTTP requests (POST, PUT, etc). 37 | - Nice side benefit: Server rendering is the only option for [Remix](https://remix.run/). It may be easier, from an infra standpoint and as a conceptual model, to migrate to Remix if the Next.js apps we're building were server-rendered. 38 | 39 | Cons 40 | 41 | - Requires infra resources to run the containerized application and all the things that come along with a server (rate limiting, auto-scaling), such as [AWS App Runner](https://aws.amazon.com/apprunner/). This can have higher costs than a prerendered site. 42 | - Higher operational and compliance burden due to the above. Requires more effort to create documentation for security approvals due to a larger attack surface. 43 | 44 | ### Prerendering 45 | 46 | Pros 47 | 48 | - Great for mostly static sites, when the markup can be generated ahead of time. 49 | - Minimal infrastructure is required. The prerendered HTML files can be served from a CDN, such as AWS CloudFront connected to an S3 bucket. Assuming other best practices are followed, like optimizing images and not loading MBs of client-side JS, this can translate to fast page loads and low costs. 50 | - Lighter operational and compliance requirements. Security approval documentation is simpler due to a smaller attack surface, and on-call responsibilities are reduced. 51 | 52 | Cons 53 | 54 | - For sites with live/personalized data, pages would require client-side JS for data fetching. This has a few downsides: 55 | - Client-side JS is required for rendering the loading, success, and error states (e.g. `fetch`, `isLoading`, `useEffect`, `catch`). Teams need to define their own code patterns to manage this (e.g. hooks, higher-order components) or install third-party dependencies (e.g. [React Query](https://react-query-v3.tanstack.com/)). This increases the amount of code to be written and maintained, and can increase code complexity. More code and complexity provide more opportunities for introducing bugs. 56 | - The prerendered HTML file is only a skeleton page in a pending state. Although the site might have a fast [First Paint](https://developer.chrome.com/docs/lighthouse/performance/first-contentful-paint), its [Time To Interactive](https://developer.chrome.com/en/docs/lighthouse/performance/interactive/) may still be slow. 57 | - [Lacks support for Middleware, Internationalized Routing, API Routes, etc](https://nextjs.org/docs/advanced-features/static-html-export%23unsupported-features). 58 | 59 | ## Links 60 | 61 | - https://www.gov.uk/service-manual/technology/using-progressive-enhancement 62 | - 🔒 [PFML comparison of current static approach vs a possible server rendering approach](https://drive.google.com/file/d/1Wgpl4q3ceJGKE5uLFH3iXUhefPxJdHcw/view) 63 | 64 | Backends for Frontends: 65 | 66 | - https://learn.microsoft.com/en-us/azure/architecture/patterns/backends-for-frontends 67 | - https://philcalcado.com/2015/09/18/the_back_end_for_front_end_pattern_bff.html 68 | 69 | Web rendering: 70 | 71 | - https://deno.com/blog/the-future-and-past-is-server-side-rendering 72 | - https://developers.google.com/web/updates/2019/02/rendering-on-the-web 73 | - https://www.smashingmagazine.com/2022/04/jamstack-rendering-patterns-evolution 74 | -------------------------------------------------------------------------------- /template/docs/decisions/{{app_name}}/0006-use-nextjs.md: -------------------------------------------------------------------------------- 1 | # Use Next.js for application framework 2 | 3 | * Status: draft 4 | * Deciders: Loren Yu, Sawyer Hollenshead, Sammy Steiner, Ali Glenesk 5 | * Date: 2023-07-14 6 | 7 | Technical Story: Add an ADR about why Next.js was selected: [ticket #166](https://github.com/navapbc/template-application-nextjs/issues/166) 8 | 9 | ## Context and Problem Statement 10 | 11 | There are many React frameworks for full-stack and front-end TypeScript/JavaScript applications. This ADR reviews several options and the pros and cons of each for the template repo. 12 | 13 | ## Decision Drivers 14 | 15 | ### Must Haves 16 | - Active Maintenance: The web framework is actively maintained with patches and minor releases delivered on a regular basis 17 | - Community of Users: The web framework has an active community of open-source users and the framework is commonly used for front-end development 18 | - Usability: The framework is relatively easy to learn for developers without prior experience in the particular framework, and there are plenty of resources and training materials available 19 | - Static Site Generation: The framework can generate static pages (HTML/CSS + JavaScript) at build time that can be cached in a CDN for faster loading 20 | - Server-Side Rendering: The framework can render some pages server-side with every request to get up-to-date information when the page loads 21 | - Client-side Rendering: The framework also supports rendering or modifying content client-side based on user interaction with the page (e.g. filtering, searching, etc.) 22 | 23 | ### Nice to Have 24 | - AuthN/AuthZ: The framework supports Authentication & Authorization routing natively or there are established extensions that provide this functionality 25 | - Internationalization (i18n): The framework supports localized routing for different languages natively or there are established extensions that provide this functionality 26 | - Middleware: The framework supports other types of middleware (i.e. functions or scripts that execute before a routing request is complete) 27 | 28 | ## Options Considered 29 | 30 | - Create React App 31 | - Next.js 32 | - Vue.js or Nuxt.js 33 | - Svelte or Sveltekit 34 | 35 | ## Decision Outcome 36 | 37 | Chosen option: Next.js, because this option meets all our technical requirements, has a large community of support, is easy to learn with good documentation, and is well understood by Nava engineers. 38 | 39 | ### Positive Consequences 40 | 41 | - Aligning on Next.js for our template repo will help teams collaborate and share work 42 | 43 | ### Negative Consequences 44 | 45 | - We'll need to modularize our code so that if Next.js ever loses support, we can swap it out 46 | - While each project will determine if Next.js is a good choice, we may end up using it for projects that aren't a good fit 47 | 48 | ## Pros and Cons of the Options 49 | 50 | ### Create React App 51 | 52 | Create React App is a lightweight, client-side, single-page application framework for React, maintained by Facebook. While it is one of the most widely adopted React frameworks, it seems like Facebook has either stopped or will stop supporting it in the near future, as it has been [removed as a suggested framework from the React website](https://github.com/reactjs/react.dev/pull/5487). 53 | 54 | - **Pros** 55 | - One of the earliest react frameworks 56 | - Widely used and understood 57 | - **Cons** 58 | - [Received only 3 commits in the past year](https://github.com/facebook/create-react-app/commits/main), raising concerns over continued support 59 | - [No longer recommended by React](https://github.com/reactjs/react.dev/pull/5487) 60 | 61 | ### Next.js 62 | 63 | Next.js is a popular full-stack framework for static and server‑rendered applications built with React and can [prerender pages it determines are static automagically](https://nextjs.org/docs/pages/building-your-application/rendering/automatic-static-optimization) alongside server-rendered routes to improve performance. It includes styling and routing solutions out of the box, is optimized for performance and SEO, and provides great developer documentation and support. 64 | 65 | Next.js is maintained by Vercel, a PaaS for front-end hosting company. 66 | 67 | - **Pros** 68 | - Popular framework with dedicated support 69 | - Supports static site generation, server-side rendering, and client-side rendering 70 | - Easy to learn and use with good documentation 71 | - [Strong Storybook support](https://storybook.js.org/recipes/next) 72 | - Often one of the first to adopt new React features (e.g. Server Components, Suspense) due to their close collaboration with the React team. 73 | - Supports [internationalized routing](https://nextjs.org/docs/pages/building-your-application/routing/internationalization), [middleware](https://nextjs.org/docs/pages/building-your-application/routing/middleware), and there are [community-maintained auth libraries](https://next-auth.js.org/). 74 | - **Cons** 75 | - Very opinionated with routing which can significantly increase code complexity for non-standard routes 76 | 77 | ### Vue.js or Nuxt.js 78 | 79 | Vue.js is an open-source, JavaScript framework for building progressive user interfaces that also supports server-side rendering. It was created by Evan You in 2014 and has grown in popularity, thanks to its reactive data binding and component-based architecture. Nuxt.js provides a set of conventions and tools for building Vue.js applications, including automatic code splitting, prefetching, and caching. 80 | 81 | - **Pros** 82 | - Static site generation is easy out of the box 83 | - Code splitting helps reduce package sizes and makes caching easier 84 | - **Cons** 85 | - Small community of support 86 | - Scalability 87 | 88 | ### Svelte or Sveltekit 89 | 90 | Svelte is a JavaScript, front-end compiler that turns declarative and easy-to-understand JavaScript code into highly efficient JavaScript code optimized for the browser. In contrast to the React framework, SvelteKit, uses a "compiler-first" approach to add server-side rendering capabilities to Svelte, eliminating the need for a virtual DOM, improving performance, and reducing bundle size. 91 | 92 | - **Pros** 93 | - Fast, performant, and very scalable 94 | - Not opinionated and very flexible 95 | - **Cons** 96 | - Relatively new framework, with fewer resources and plugins than React 97 | - Steeper learning curve, since many JavaScript developers are familiar with React 98 | - Small community 99 | - Limited documentation 100 | 101 | ## Links 102 | 103 | - [React removing create react app from its recommendations](https://github.com/reactjs/react.dev/pull/5487) 104 | -------------------------------------------------------------------------------- /template/docs/decisions/{{app_name}}/0007-i18n-type-safety.md: -------------------------------------------------------------------------------- 1 | # Enable i18next type checks 2 | 3 | - Status: accepted 4 | - Deciders: Sawyer Hollenshead, Loren Yu 5 | - Date: 2023-08-28 6 | 7 | ## Context and Problem Statement 8 | 9 | The codebase utilizes I18next as its internationalization library. [I18next provides the option to enable type checking](https://www.i18next.com/overview/typescript), to help catch references to invalid i18n key paths. This is a feature that projects at Nava have desired before, but the path to implement the feature isn't always the clearest to teams. 10 | 11 | ## Considered Options 12 | 13 | - Enable type checking of I18next 14 | - Do not enable type checking of I18next 15 | 16 | ## Decision Outcome 17 | 18 | Chosen option: Enable type checking of I18next. 19 | 20 | ### Positive Consequences 21 | 22 | - Type checking will help catch references to invalid i18n key paths, which will help prevent teams from shipping experiences with missing content. Migration efforts that affect locale files will be less risky as a result. 23 | - Developer tooling will be able to provide better autocomplete suggestions for i18n key paths. 24 | 25 | ### Negative Consequences 26 | 27 | - A TypeScript file representing the i18n namespaces must be maintained alongside the JSON locale files. This file will need to be updated whenever a new locale file is added or removed. If the file isn't updated, a developer will receive type errors when they reference a valid i18n key path. This could be a source of confusion for developers who aren't familiar with the i18next type-checking feature. 28 | - This file [can be generated](https://github.com/i18next/i18next-resources-for-ts). 29 | - Tests can be added to catch missing namespaces in the file. 30 | - TypeScript errors, particularly those related to i18n, can be difficult to understand. This could slow down developers. 31 | - Compilation time may be impacted depending on a project's size 32 | - Type checking only works when the locale files are collocated with the code. If you have your files stored outside of the repo (e.g. in a translation service), then you can either (a) disable type checking by removing the type checking feature, or (b) [follow the instructions in this post](https://dev.to/adrai/supercharge-your-typescript-app-mastering-i18next-for-type-safe-translations-2idp) about generating the TypeScript file. 33 | -------------------------------------------------------------------------------- /template/docs/decisions/{{app_name}}/0008-app-router.md: -------------------------------------------------------------------------------- 1 | # Use the Next.js App Router instead of Pages Router 2 | 3 | - Status: Accepted 4 | - Deciders: Sawyer Hollenshead, Ali Glenesk, Rocket Lee, Loren Yu 5 | - Date: 2023-11-28 6 | 7 | ## Context and Problem Statement 8 | 9 | Beginning in [Next.js version 13](https://nextjs.org/blog/next-13), Next.js now has two different routers: 10 | 11 | 1. App Router: a newer router that allows you to use React's latest features, such as [Server Components](https://nextjs.org/docs/app/building-your-application/rendering/server-components), [Server-only Forms](https://nextjs.org/docs/app/building-your-application/data-fetching/forms-and-mutations), and [Streaming](https://nextjs.org/docs/app/building-your-application/routing/loading-ui-and-streaming). 12 | 2. Pages Router: the original Next.js router, which continues to be supported for older Next.js applications. 13 | 14 | The codebase originally used the older Pages Router since that was all that existed at the time. 15 | 16 | The App Router is a paradigm shift for React and comes with a number of notable differences in how data is fetched and mutated, and how code is organized. Although the Pages Router is still supported, all new React features, like server components and server-only forms, are so far only supported when using the App Router. 17 | 18 | ## Considered Options 19 | 20 | - Stick with the Pages Router 21 | - Switch to the App Router 22 | 23 | ## Decision Outcome 24 | 25 | Use the App Router moving forward. As a result, the codebase will also switch from `i18next` to `next-intl` to support locale detection and internationalized routing. 26 | 27 | ### Positive Consequences 28 | 29 | - Projects will be able to take advantage of the latest React features, such as server components and server-only forms. 30 | - As a result, projects will default to server-only rendering, which reduces client-side bundle sizes (improves performance) and can reduce network waterfalls. 31 | 32 | ### Negative Consequences 33 | 34 | - Unlike the Pages Router, the App Router doesn't provide built-in support for locale detection or internationalized routing. The codebase will switch from `i18next` to `next-intl` to support locale detection and internationalized routing, in addition to internationalized content. 35 | - The App Router has a more [complex caching strategy](https://nextjs.org/docs/app/building-your-application/caching) that has a learning curve. The codebase will provide documentation to help mitigate this. 36 | 37 | ## Links 38 | 39 | - [🔒 Nava tech spec detailing a migration from Pages Router to App Router](https://docs.google.com/document/d/1elHojRhDdUUotsEAVCpX0y3igr22rRdZCwNBeUtO9c0/edit) 40 | - [Making Sense of React Server Components](https://www.joshwcomeau.com/react/server-components/) 41 | - [How to Think About Security in Next.js](https://nextjs.org/blog/security-nextjs-server-components-actions) 42 | -------------------------------------------------------------------------------- /template/docs/{{app_name}}/image-optimization.md: -------------------------------------------------------------------------------- 1 | # Image optimization 2 | 3 | If your project includes image content that doesn't lend itself to vector formats like SVG, you may want to use the [image optimization functionality that Next.js provides](https://nextjs.org/docs/app/building-your-application/optimizing/images), in order to better optimize your image(s) for improved performance and UX. 4 | 5 | When running in production (not when running `next dev`), [Next.js recommends installing the `sharp` library](https://nextjs.org/docs/app/building-your-application/deploying#image-optimization): 6 | 7 | ```sh 8 | npm i sharp 9 | ``` 10 | -------------------------------------------------------------------------------- /template/docs/{{app_name}}/internationalization.md: -------------------------------------------------------------------------------- 1 | # Internationalization (i18n) 2 | 3 | - [next-intl](https://next-intl-docs.vercel.app) is used for internationalization. Toggling between languages is done by changing the URL's path prefix (e.g. `/about` ➡️ `/es-US/about`). 4 | - Configuration is located in `i18n/config.ts`. For the most part, you shouldn't need to edit this file unless adding a new formatter or new language. 5 | 6 | ## Managing translations 7 | 8 | - Translations are managed as files in the `i18n/messages/` directory, where each language has its own directory (e.g. `en-US` and `es-US`). 9 | - How you organize translations is up to you, but here are some suggestions: 10 | - Group your messages. It's recommended to use component/page names as namespaces and embrace them as the primary unit of organization in your app. 11 | - By default, all messages are in a single file, but you can split them into multiple files if you prefer. Continue to export all messages from `i18n/messages/{locale}/index.ts` so that they can be imported from a single location, and so files that depend on the messages don't need to be updated. 12 | - There are a number of built-in formatters based on [JS's `Intl` API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl) that can be used in locale strings, and custom formatters can be added as well. [See the formatting docs for details](https://next-intl-docs.vercel.app/docs/usage/numbers). 13 | - If a string's translation is missing in another language, the default language (usually English) will be used as a fallback. 14 | 15 | ### Type-safe translations 16 | 17 | The app is configured to report errors if you attempt to reference an i18n key path that doesn't exist in a locale file. 18 | 19 | [Learn more about using TypeScript with next-intl](https://next-intl-docs.vercel.app/docs/workflows/typescript). 20 | 21 | ## Load translations 22 | 23 | Locale messages should only ever be loaded on the server-side, to avoid bloating the client-side bundle. If a client component needs to access translations, only the messages required by that component should be passed into it. 24 | 25 | [See the Internationalization of Server & Client Components docs](https://next-intl-docs.vercel.app/docs/environments/server-client-components) for more details. 26 | 27 | ## Add a new language 28 | 29 | 1. Add a language folder, using the same BCP47 language tag: `mkdir -p src/i18n/messages/` 30 | 1. Add a language file: `touch src/i18n/messages//index.ts` and add the translated content. The JSON structure should be the same across languages. However, non-default languages can omit keys, in which case the default language will be used as a fallback. 31 | 1. Update `i18n/config.ts` to include the new language in the `locales` array. 32 | 33 | ## Structuring your messages 34 | 35 | In terms of best practices, [it is recommended](https://next-intl-docs.vercel.app/docs/usage/messages#structuring-messages) to structure your messages such that they correspond to the component that will be using them. You can nest these definitions arbitrarily deep if you have a particularly complex need. 36 | 37 | It is always preferable to structure messages per their usage rather than due to some side effect of a technological implementation. The idea is to group them semantically but also preserve maximum flexibility for a translator. For instance, splitting up a paragraph in order to separate out a link might lead to awkward translation, so it is best to keep it as a single message. The info below shows techniques for common needs that prevent unnecessary splits of content. 38 | 39 | ### Variables 40 | 41 | Messages do not need to be split in order to incorporate dynamic data. Instead, these can be inserted via the [interpolation functionality](https://next-intl-docs.vercel.app/docs/usage/messages#interpolation-of-dynamic-values): 42 | 43 | ```json 44 | "message": "Hello {name}!" 45 | ``` 46 | 47 | ```tsx 48 | t('message', {name: 'Jane'}); // "Hello Jane!" 49 | ``` 50 | 51 | ### Rich text messages 52 | 53 | If your app needs a particular chunk of content to contain something other than plain text (such as links, formatting, or a custom component), you can utilize the "rich text" functionality ([see docs](https://next-intl-docs.vercel.app/docs/usage/messages#rich-text)). This allows one to embed arbitrary custom tags into the translation content strings and specify how each of those should be handled. 54 | 55 | Example from their docs: 56 | ```json 57 | { 58 | "message": "Please refer to the guidelines." 59 | } 60 | ``` 61 | 62 | ```tsx 63 | // Returns `<>Please refer to the guidelines.` 64 | t.rich('message', { 65 | guidelines: (chunks) => {chunks} 66 | }); 67 | ``` 68 | 69 | If you have something that you are going to use repeatedly throughout your app, you can specify it in the [`defaultTranslationValues` config](https://next-intl-docs.vercel.app/docs/usage/configuration#default-translation-values). 70 | 71 | ### Other needs 72 | 73 | For examples of other functionality such as pluralization, arrays of content, etc, please [see the docs](https://next-intl-docs.vercel.app/docs/usage/messages). 74 | -------------------------------------------------------------------------------- /template/docs/{{app_name}}/security.md: -------------------------------------------------------------------------------- 1 | # Security 2 | 3 | Next.js blurs the line between server and client-side code, which can lead to security vulnerabilities if not properly managed. Read the [Next.js Security tips](https://nextjs.org/docs/app/building-your-application/deploying/production-checklist#security) for tips. 4 | 5 | ## Server-only code 6 | 7 | The app makes available the `server-only` package, which can be imported into a file to ensure that it is only executed on the server. 8 | 9 | Developers receive a build-time error if they ever accidentally import one of these modules into a Client Component. This can be useful for files where you want to ensure that the code is only executed on the server, such as when interacting with a database. For example: 10 | 11 | ```ts 12 | import "server-only"; 13 | 14 | await db.query("SELECT * FROM users"); 15 | ``` 16 | 17 | [More detail here](https://nextjs.org/docs/app/building-your-application/rendering/composition-patterns#keeping-server-only-code-out-of-the-client-environment). 18 | -------------------------------------------------------------------------------- /template/{{app_name}}/.dockerignore: -------------------------------------------------------------------------------- 1 | # The docker container should install its own packages. 2 | node_modules 3 | 4 | # The docker container should ignore next builds. 5 | .next 6 | -------------------------------------------------------------------------------- /template/{{app_name}}/.env.development: -------------------------------------------------------------------------------- 1 | # !! Don't put secrets in this file. Use .env.local for secrets instead !! 2 | # This file is checked into your git repo and provides defaults for non-sensitive 3 | # env vars when running `next dev` only. 4 | # Learn more: https://nextjs.org/docs/app/building-your-application/configuring/environment-variables 5 | 6 | # If you deploy to a subpath, change this to the subpath so relative paths work correctly. 7 | NEXT_PUBLIC_BASE_PATH= 8 | -------------------------------------------------------------------------------- /template/{{app_name}}/.env.jinja: -------------------------------------------------------------------------------- 1 | # !! Don't put secrets in this file. Use .env.local for secrets instead !! 2 | # This file is checked into your git repo and provides defaults for non-sensitive 3 | # env vars when running `next` commands. 4 | # Learn more: https://nextjs.org/docs/app/building-your-application/configuring/environment-variables 5 | PORT={{ app_local_port }} 6 | -------------------------------------------------------------------------------- /template/{{app_name}}/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: [ 4 | "eslint:recommended", 5 | "plugin:storybook/recommended", 6 | "plugin:you-dont-need-lodash-underscore/compatible", 7 | // Disable ESLint code formatting rules which conflict with Prettier 8 | "prettier", 9 | // `next` should be extended last according to their docs 10 | // https://nextjs.org/docs/basic-features/eslint 11 | "next/core-web-vitals", 12 | ], 13 | rules: { 14 | // Next.js component is useful for optimizing images, but also requires additional 15 | // dependencies to work in standalone mode. It may be overkill for most projects at 16 | // Nava which aren't image heavy. 17 | "@next/next/no-img-element": "off", 18 | }, 19 | // Additional lint rules. These get layered onto the top-level rules. 20 | overrides: [ 21 | // Lint config specific to Test files 22 | { 23 | files: ["tests/**/?(*.)+(spec|test).[jt]s?(x)"], 24 | plugins: ["jest"], 25 | extends: [ 26 | "plugin:jest/recommended", 27 | "plugin:jest-dom/recommended", 28 | "plugin:testing-library/react", 29 | ], 30 | rules: { 31 | "no-restricted-imports": [ 32 | "error", 33 | { 34 | paths: [ 35 | { 36 | message: 37 | 'Import from "tests/react-utils" instead so that translations work.', 38 | name: "@testing-library/react", 39 | }, 40 | ], 41 | }, 42 | ], 43 | }, 44 | }, 45 | // Lint config specific to TypeScript files 46 | { 47 | files: "**/*.+(ts|tsx)", 48 | excludedFiles: [".storybook/*.ts?(x)"], 49 | parserOptions: { 50 | // These paths need defined to support rules that require type information 51 | tsconfigRootDir: __dirname, 52 | project: ["./tsconfig.json"], 53 | }, 54 | extends: [ 55 | "plugin:@typescript-eslint/recommended", 56 | // Disable vanilla ESLint rules that conflict with those in @typescript-eslint 57 | "plugin:@typescript-eslint/eslint-recommended", 58 | // Rules that specifically require type information 59 | "plugin:@typescript-eslint/recommended-requiring-type-checking", 60 | ], 61 | plugins: ["@typescript-eslint"], 62 | rules: { 63 | // Prevent dead code accumulation 64 | "@typescript-eslint/no-unused-vars": "error", 65 | // The usage of `any` defeats the purpose of typescript. Consider using `unknown` type instead instead. 66 | "@typescript-eslint/no-explicit-any": "error", 67 | }, 68 | }, 69 | ], 70 | settings: { 71 | // Support projects where Next.js isn't installed in the root directory (such as a monorepo) 72 | next: { 73 | rootDir: __dirname, 74 | }, 75 | }, 76 | }; 77 | -------------------------------------------------------------------------------- /template/{{app_name}}/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | .swc 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | .pnpm-debug.log* 26 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | 36 | # storybook 37 | /storybook-static 38 | 39 | # compiled css 40 | /public/css 41 | 42 | # uswds assets 43 | /public/uswds 44 | 45 | # Developer-specific IDE settings 46 | .vscode 47 | -------------------------------------------------------------------------------- /template/{{app_name}}/.grype.yml: -------------------------------------------------------------------------------- 1 | # List of vulnerabilities to ignore for the anchore scan 2 | # https://github.com/anchore/grype#specifying-matches-to-ignore 3 | # More info can be found in the docs/infra/vulnerability-management.md file 4 | 5 | # Please add safelists in the following format to make it easier when checking 6 | # Package/module name: URL to vulnerability for checking updates 7 | # Versions: URL to the version history 8 | # Dependencies: Name of any other packages or modules that are dependent on this version 9 | # Link to the dependencies for ease of checking for updates 10 | # Issue: Why there is a finding and why this is here or not been removed 11 | # Last checked: Date last checked in scans 12 | # - vulnerability: The-CVE-or-vuln-id # Remove comment at start of line 13 | 14 | ignore: 15 | # These settings ignore any findings that fall into these categories 16 | - fix-state: not-fixed 17 | - fix-state: wont-fix 18 | - fix-state: unknown 19 | 20 | # glob-parent before 5.1.2 vulnerable to Regular Expression Denial of Service in enclosure regex 21 | # https://github.com/advisories/GHSA-ww39-953v-wcq6 22 | # High severity 23 | # Ignoring since this is only a dependency of dev tools: storybook (for storybook docs site), 24 | # eslint (for Linting in CI), and sass (for compiling CSS during CI build phase) 25 | - vulnerability: GHSA-ww39-953v-wcq6 26 | 27 | # Regular Expression Denial of Service in trim 28 | # https://github.com/advisories/GHSA-w5p7-h5w8-2hfq 29 | # High severity 30 | # Ignoring since this is only used in storybook which is a dev tool 31 | - vulnerability: GHSA-w5p7-h5w8-2hfq 32 | 33 | # Uncontrolled Resource Consumption in trim-newlines 34 | # https://github.com/advisories/GHSA-7p7h-4mm5-852v 35 | # Ignoring since this is only used in storybook which is a dev tool 36 | - vulnerability: GHSA-7p7h-4mm5-852v 37 | 38 | ##################### 39 | ## False positives ## 40 | ##################### 41 | 42 | # http-cache-semantics vulnerable to Regular Expression Denial of Service 43 | # https://github.com/advisories/GHSA-rc47-6667-2j5j 44 | # http-cache-semantics does not exist as a dependency in this app 45 | - vulnerability: GHSA-rc47-6667-2j5j 46 | -------------------------------------------------------------------------------- /template/{{app_name}}/.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignoring generated files 2 | .next/ 3 | node_modules/ 4 | storybook-static/ 5 | src/types/generated* 6 | 7 | # Ignore USWDS static assets 8 | public/uswds 9 | 10 | # Test artifacts 11 | coverage/ 12 | -------------------------------------------------------------------------------- /template/{{app_name}}/.prettierrc.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | /** @type {import("@ianvs/prettier-plugin-sort-imports").PrettierConfig} */ 4 | module.exports = { 5 | /** 6 | * Sort imports so that the order is roughly: 7 | * - Built-in Node modules (`import ... from "fs"`) 8 | * - Third-party modules (`import ... from "lodash"`) 9 | * - Third-party modules that often export React components or hooks 10 | * - Local components 11 | * - All other files 12 | * @see https://github.com/IanVS/prettier-plugin-sort-imports 13 | */ 14 | importOrder: [ 15 | "", 16 | "", 17 | "", // blank line 18 | "^next[/-](.*)$", 19 | "^react$", 20 | "uswds", 21 | "", // blank line 22 | "^(src/)?components/(.*)$", 23 | "^[./]", 24 | ], 25 | importOrderTypeScriptVersion: "5.0.0", 26 | }; 27 | -------------------------------------------------------------------------------- /template/{{app_name}}/.storybook/I18nStoryWrapper.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Storybook decorator, enabling internationalization for each story. 3 | * @see https://storybook.js.org/docs/writing-stories/decorators 4 | */ 5 | import { StoryContext } from "@storybook/react"; 6 | 7 | import { NextIntlClientProvider } from "next-intl"; 8 | import React from "react"; 9 | 10 | import { defaultLocale, formats, timeZone } from "../src/i18n/config"; 11 | 12 | const I18nStoryWrapper = ( 13 | Story: React.ComponentType, 14 | context: StoryContext, 15 | ) => { 16 | const locale = context.globals.locale ?? defaultLocale; 17 | 18 | return ( 19 | 25 | 26 | 27 | ); 28 | }; 29 | 30 | export default I18nStoryWrapper; 31 | -------------------------------------------------------------------------------- /template/{{app_name}}/.storybook/main.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Storybook's main configuration file that controls the generation of Storybook. 3 | * Handles things like config for location of story files and managing presets (which configure webpack and babel). 4 | * @see https://storybook.js.org/docs/configurations/default-config/ 5 | */ 6 | // @ts-check 7 | 8 | import path from "path"; 9 | 10 | // Support deploying to a subdirectory, such as GitHub Pages. 11 | const NEXT_PUBLIC_BASE_PATH = process.env.NEXT_PUBLIC_BASE_PATH ?? ""; 12 | 13 | /** 14 | * @type {import("@storybook/nextjs").StorybookConfig} 15 | */ 16 | const config = { 17 | stories: [ 18 | "../stories/**/*.@(mdx|stories.@(js|jsx|ts|tsx))", 19 | "../src/**/*.@(mdx|stories.@(js|jsx|ts|tsx))", 20 | ], 21 | docs: { 22 | autodocs: true, 23 | }, 24 | addons: ["@storybook/addon-essentials"], 25 | framework: { 26 | // https://storybook.js.org/docs/get-started/nextjs 27 | name: "@storybook/nextjs", 28 | options: { 29 | nextConfigPath: path.resolve(import.meta.dirname, "../next.config.js"), 30 | builder: { 31 | // lazyCompilation breaks Storybook when running from within Docker 32 | // Google Translate this page for context: https://zenn.dev/yutaosawa/scraps/7764e5f17173d1 33 | lazyCompilation: false, 34 | }, 35 | }, 36 | }, 37 | core: { 38 | disableTelemetry: true, 39 | }, 40 | staticDirs: ["../public"], 41 | // Support deploying Storybook to a subdirectory (like GitHub Pages). 42 | // This makes `process.env.NEXT_PUBLIC_BASE_PATH` available to our source code. 43 | env: (config) => ({ 44 | ...config, 45 | NEXT_PUBLIC_BASE_PATH, 46 | }), 47 | }; 48 | 49 | export default config; 50 | -------------------------------------------------------------------------------- /template/{{app_name}}/.storybook/manager-head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /template/{{app_name}}/.storybook/preview.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Setup the toolbar, styling, and global context for each Storybook story. 3 | * @see https://storybook.js.org/docs/configure#configure-story-rendering 4 | */ 5 | import { Loader, Preview } from "@storybook/react"; 6 | 7 | import "../src/styles/styles.scss"; 8 | 9 | import { defaultLocale, locales } from "../src/i18n/config"; 10 | import { getMessagesWithFallbacks } from "../src/i18n/getMessagesWithFallbacks"; 11 | import I18nStoryWrapper from "./I18nStoryWrapper"; 12 | 13 | const parameters = { 14 | nextjs: { 15 | appDirectory: true, 16 | }, 17 | controls: { 18 | matchers: { 19 | color: /(background|color)$/i, 20 | date: /Date$/, 21 | }, 22 | }, 23 | options: { 24 | storySort: { 25 | method: "alphabetical", 26 | order: [ 27 | "Welcome", 28 | "Core", 29 | // Storybook infers the title when not explicitly set, but is case-sensitive 30 | // so we need to explicitly set both casings here for this to properly sort. 31 | "Components", 32 | "components", 33 | "Templates", 34 | "Pages", 35 | "pages", 36 | ], 37 | }, 38 | }, 39 | }; 40 | 41 | const i18nMessagesLoader: Loader = async (context) => { 42 | const messages = await getMessagesWithFallbacks(context.globals.locale); 43 | return { messages }; 44 | }; 45 | 46 | const preview: Preview = { 47 | loaders: [i18nMessagesLoader], 48 | decorators: [I18nStoryWrapper], 49 | parameters, 50 | globalTypes: { 51 | locale: { 52 | description: "Active language", 53 | defaultValue: defaultLocale, 54 | toolbar: { 55 | icon: "globe", 56 | items: locales, 57 | }, 58 | }, 59 | }, 60 | }; 61 | 62 | export default preview; 63 | -------------------------------------------------------------------------------- /template/{{app_name}}/Dockerfile: -------------------------------------------------------------------------------- 1 | # This file is largely based on the template-application-flask Dockerfile and 2 | # Next.js Docker example: https://github.com/vercel/next.js/blob/canary/examples/with-docker-compose 3 | # ============================================================================= 4 | FROM node:20.19.2-bullseye-slim AS base 5 | WORKDIR /app 6 | 7 | # Install dependencies 8 | COPY package.json package-lock.json ./ 9 | COPY public ./public 10 | COPY scripts ./scripts 11 | RUN npm ci --no-audit 12 | 13 | # ============================================================================= 14 | # Development stage 15 | # ============================================================================= 16 | FROM base AS dev 17 | WORKDIR /app 18 | 19 | COPY tsconfig.json . 20 | COPY *.config.js . 21 | COPY *.d.ts . 22 | COPY src ./src 23 | COPY stories ./stories 24 | COPY .storybook ./.storybook 25 | 26 | ENV NEXT_TELEMETRY_DISABLED 1 27 | 28 | CMD ["npm", "run", "dev"] 29 | 30 | # ============================================================================= 31 | # Release stage 32 | # ============================================================================= 33 | 34 | # Build the Next.js app 35 | # ===================================== 36 | FROM base AS builder 37 | WORKDIR /app 38 | 39 | COPY tsconfig.json . 40 | COPY *.config.js . 41 | COPY *.d.ts . 42 | COPY src ./src 43 | 44 | # Environment variables must be present at build time 45 | # https://github.com/vercel/next.js/discussions/14030 46 | # ARG ENV_VARIABLE 47 | # ENV ENV_VARIABLE=${ENV_VARIABLE} 48 | # ARG NEXT_PUBLIC_ENV_VARIABLE 49 | # ENV NEXT_PUBLIC_ENV_VARIABLE=${NEXT_PUBLIC_ENV_VARIABLE} 50 | 51 | ENV NEXT_TELEMETRY_DISABLED 1 52 | 53 | # Skip lint because it should have happened in the CI already 54 | RUN npm run build -- --no-lint 55 | 56 | # Run the Next.js server 57 | # ===================================== 58 | # Use clean image for release, excluding any unnecessary files or dependencies 59 | FROM node:20.19.2-bullseye-slim AS release 60 | WORKDIR /app 61 | 62 | RUN apt-get update \ 63 | # Install security updates 64 | # https://pythonspeed.com/articles/security-updates-in-docker/ 65 | && apt-get upgrade --yes \ 66 | # Install wget, required for health checks 67 | wget \ 68 | # Reduce the image size by clearing apt cached lists 69 | && rm -fr /var/lib/apt/lists/* \ 70 | # Release stage doesn't have a need for `npm`, so remove it to avoid 71 | # any vulnerabilities specific to NPM 72 | && npm uninstall -g npm \ 73 | # Remove yarn as well (https://github.com/nodejs/docker-node/issues/777) 74 | && rm -rf /opt/yarn-v*\ 75 | # Don't run production as root 76 | && addgroup --system --gid 1001 nodejs \ 77 | && adduser --system --uid 1001 nextjs \ 78 | # Support cache 79 | && mkdir -p .next/cache/images/ \ 80 | && chown nextjs:nodejs .next/cache/images/ 81 | 82 | USER nextjs 83 | 84 | COPY --from=builder /app/public ./public 85 | 86 | # Automatically leverage output traces to reduce image size 87 | # https://nextjs.org/docs/advanced-features/output-file-tracing 88 | COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ 89 | COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static 90 | 91 | # Environment variables must be redefined at run time 92 | # ARG ENV_VARIABLE 93 | # ENV ENV_VARIABLE=${ENV_VARIABLE} 94 | # ARG NEXT_PUBLIC_ENV_VARIABLE 95 | # ENV NEXT_PUBLIC_ENV_VARIABLE=${NEXT_PUBLIC_ENV_VARIABLE} 96 | ENV NEXT_TELEMETRY_DISABLED 1 97 | ENV PORT 3000 98 | 99 | EXPOSE 3000 100 | 101 | # hadolint ignore=DL3025 102 | CMD HOSTNAME="0.0.0.0" node server.js 103 | -------------------------------------------------------------------------------- /template/{{app_name}}/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: release-build dev storybook stop pre-dev 2 | 3 | include Makefile.config 4 | 5 | # Docker user configuration 6 | # This logic is to avoid issues with permissions and mounting local volumes, 7 | # which should be owned by the same UID for Linux distros. Mac OS can use root, 8 | # but it is best practice to run things as with least permission where possible 9 | 10 | # Can be set by adding user= and/ or uid= after the make command 11 | # If variables are not set explicitly: try looking up values from current 12 | # environment, otherwise fixed defaults. 13 | # uid= defaults to 0 if user= set (which makes sense if user=root, otherwise you 14 | # probably want to set uid as well). 15 | ifeq ($(user),) 16 | RUN_USER ?= $(or $(strip $(USER)),nodummy) 17 | RUN_UID ?= $(or $(strip $(shell id -u)),4000) 18 | else 19 | RUN_USER = $(user) 20 | RUN_UID = $(or $(strip $(uid)),0) 21 | endif 22 | 23 | export RUN_USER 24 | export RUN_UID 25 | 26 | ################################################## 27 | # Release 28 | ################################################## 29 | release-build: 30 | docker buildx build \ 31 | --target release \ 32 | --platform=linux/amd64 \ 33 | --build-arg RUN_USER=$(RUN_USER) \ 34 | --build-arg RUN_UID=$(RUN_UID) \ 35 | $(OPTS) \ 36 | . 37 | 38 | ################################################## 39 | # Local development 40 | ################################################## 41 | container-npm-install: # Install NPM packages from within Docker 42 | @# Install npm packages using Docker if node_modules doesn't exist 43 | @if [ ! -d "node_modules" ]; then \ 44 | echo "Installing npm packages using Docker. This can take several minutes without output..."; \ 45 | docker run --rm -v "$(PWD)":/usr/src/app -w /usr/src/app node:20-bullseye-slim npm install; \ 46 | else \ 47 | echo "node_modules directory already exists, skipping npm install"; \ 48 | fi 49 | 50 | dev: # Run the Next.js local dev server in Docker 51 | docker compose up --detach $(APP_NAME) 52 | docker compose logs --follow $(APP_NAME) 53 | 54 | storybook: # Run the Storybook local dev server in Docker 55 | docker compose up --detach $(APP_NAME)-storybook 56 | docker compose logs --follow $(APP_NAME)-storybook 57 | 58 | stop: 59 | docker compose down 60 | -------------------------------------------------------------------------------- /template/{{app_name}}/Makefile.config.jinja: -------------------------------------------------------------------------------- 1 | APP_NAME := {{ app_name }} 2 | -------------------------------------------------------------------------------- /template/{{app_name}}/README.md.jinja: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | - This is a [Next.js](https://nextjs.org/) React web application, written in [TypeScript](https://www.typescriptlang.org/). 4 | - [U.S. Web Design System](https://designsystem.digital.gov) provides themeable styling and a set of common components. 5 | - [React-USWDS](https://github.com/trussworks/react-uswds) provides React components already with USWDS theming out of the box. For a reference point starting out, see `react-uswds-hello.tsx` which includes examples of react-uswds component usage. 6 | - [Storybook](https://storybook.js.org/) is included as a frontend workshop. 7 | 8 | ### Directory structure 9 | 10 | ``` 11 | ├── .storybook # Storybook configuration 12 | ├── public # Static assets 13 | ├── src # Source code 14 | │ ├── app # Routes, layouts, and loading screens 15 | │ │ ├── api # Custom request handlers 16 | │ │ ├── layout.tsx # Root layout, wraps every page 17 | │ │ └── page.tsx # Homepage 18 | | ├── adapters # External service adapters 19 | │ ├── components # Reusable UI components 20 | │ ├── i18n # Internationalization 21 | │ │ ├── config.ts # Supported locales, timezone, and formatters 22 | │ │ └── messages # Translated strings 23 | │ ├── styles # Sass & design system settings 24 | │ └── types # TypeScript type declarations 25 | ├── stories # Storybook pages 26 | └── tests # Test setup and helpers 27 | ``` 28 | 29 | ## 💻 Development 30 | 31 | [Next.js](https://nextjs.org/docs) provides the React framework for building the web application. Routes are defined in the `app/` directory. Pages are automatically routed based on the directory name. For example, `app/[locale]/about/page.tsx` would render at `/about` (for English) or `/es-US/about` (for Spanish). 32 | 33 | [**Learn more about developing Next.js applications** ↗️](https://nextjs.org/docs) 34 | 35 | ### Getting started 36 | 37 | The application can be ran natively or in a Docker container. 38 | 39 | #### Native 40 | 41 | From the `{{ app_name }}/` directory: 42 | 43 | 1. Install dependencies 44 | ```bash 45 | npm install 46 | ``` 47 | 1. Optionally, disable [telemetry data collection](https://nextjs.org/telemetry) 48 | ```bash 49 | npx next telemetry disable 50 | ``` 51 | 1. Run the local development server 52 | ```bash 53 | npm run dev 54 | ``` 55 | 1. Navigate to [localhost:{{ app_local_port }}](http://localhost:{{ app_local_port }}) to view the application 56 | 57 | ##### Other scripts 58 | 59 | - `npm run build` - Builds the production Next.js bundle 60 | - `npm start` - Runs the Next.js server, after building the production bundle 61 | 62 | #### Docker 63 | 64 | Alternatively, you can run the application in a Docker container. 65 | 66 | From the `{{ app_name }}/` directory: 67 | 68 | 1. (Optional) If your machine doesn't include Node, and you'd like tools like VS Code to provide intellisense & type checking, run the following command to install the packages locally: 69 | ```bash 70 | make container-npm-install 71 | ``` 72 | 1. Run the local development server 73 | ```bash 74 | make dev 75 | ``` 76 | 1. Navigate to [localhost:{{ app_local_port }}](http://localhost:{{ app_local_port }}) to view the application 77 | 78 | ##### Other scripts 79 | 80 | - `make release-build` - Creates the Docker image for deployment to the cloud 81 | 82 | ## 🖼️ Storybook 83 | 84 | Storybook is a [frontend workshop](https://bradfrost.com/blog/post/a-frontend-workshop-environment/) for developing and documenting pages and components in isolation. It allows you to render the same React components and files in the `src/` directory in a browser, without the need for a server or database. This allows you to develop and manually test components without having to run the entire Next.js application. 85 | 86 | See the [Storybook Next.js documentation](https://github.com/storybookjs/storybook/tree/next/code/frameworks/nextjs) for more information about using Storybook with Next.js 87 | 88 | Similar to the Next.js application, Storybook can be ran natively or in a Docker container. 89 | 90 | #### Native 91 | 92 | From the `{{ app_name }}/` directory: 93 | 94 | 1. `npm run storybook` 95 | 2. Navigate to [localhost:6006](http://localhost:6006) to view 96 | 97 | ##### Other scripts 98 | 99 | - `npm run storybook-build` - Exports a static site to `storybook-static/` 100 | 101 | #### Docker 102 | 103 | Alternatively, you can run Storybook in a Docker container. 104 | 105 | From the `{{ app_name }}/` directory: 106 | 107 | 1. `make storybook` 108 | 2. Navigate to [localhost:6006](http://localhost:6006) to view 109 | 110 | ## 🐛 Testing 111 | 112 | [Jest](https://jestjs.io/docs/getting-started) is used as the test runner. Tests are managed as `.test.ts` (or `.test.tsx`) files and are colocated with the files they reference (for unit tests). 113 | 114 | To run tests: 115 | 116 | - `npm test` - Runs all tests and outputs test coverage report 117 | - `npm run test-update` - Updates test snapshots 118 | - `npm run test-watch` - Runs tests in [watch](https://jestjs.io/docs/cli#--watch) mode. Tests will re-run when files are changed, and an interactive prompt will allow you to run specific tests or update snapshots. 119 | 120 | A subset of tests can be ran by passing a pattern to the script. For example, to only run tests in `src/components`: 121 | 122 | ```sh 123 | npm run test-watch -- src/components 124 | ``` 125 | 126 | ### Testing React components 127 | 128 | [React Testing Library (RTL)](https://testing-library.com/docs/react-testing-library/intro) provides the utilities for rendering and querying, and [`jest-axe`](https://www.npmjs.com/package/jest-axe) is used for accessibility testing. Refer to their docs to learn more about their APIs, or view an existing test for examples. 129 | 130 | `@testing-library/react` methods should be imported from `tests/react-utils` in order for internationalization to work within your tests: 131 | 132 | ```diff 133 | - import { render, screen } from '@testing-library/react'; 134 | + import { render, screen } from 'tests/react-utils'; 135 | 136 | it("renders submit button", () => { 137 | render() 138 | 139 | expect( 140 | screen.getByRole("button", { name: "Submit" }) 141 | ).toBeInTheDocument() 142 | }) 143 | ``` 144 | 145 | ## 🤖 Type checking, linting, and formatting 146 | 147 | - [TypeScript](https://www.typescriptlang.org/) is used for type checking. 148 | - `npm run ts:check` - Type checks all files 149 | - `npm run i18n-types` - Updates the i18n TypeScript declaration. You only need to run this if you've added a new English locale file (JSON files in `public/locales/en-US`). This runs automatically when you start the development server or build the application. 150 | - [ESLint](https://eslint.org/) is used for linting. This helps catch common mistakes and encourage best practices. 151 | - `npm run lint` - Lints all files and reports any errors 152 | - `npm run lint-fix` - Lints all files and fixes any auto-fixable errors 153 | - [Prettier](https://prettier.io/) is used for code formatting. This reduces the need for manual formatting or nitpicking and enforces a consistent style. 154 | - `npm run format`: Formats all files 155 | - `npm run format-check`: Check files for formatting violations without fixing them. 156 | 157 | Optionally, configure your code editor to auto run these tools on file save. Most code editors have plugins for these tools or provide native support. 158 | 159 |
160 | VSCode instructions 161 | 162 | 1. Install the [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) and [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) extensions. 163 | 2. Add the following to a `.vscode/settings.json` file, in whichever directory you open in VSCode (root or this directory): 164 | 165 | ```json 166 | { 167 | "editor.codeActionsOnSave": { 168 | "source.fixAll.eslint": true 169 | }, 170 | "editor.formatOnSave": true, 171 | "editor.defaultFormatter": "esbenp.prettier-vscode", 172 | "eslint.workingDirectories": ["./{{ app_name }}"], 173 | "typescript.validate.enable": true 174 | } 175 | ``` 176 | 177 | [Learn more about these settings](https://code.visualstudio.com/docs/getstarted/settings) 178 | 179 |
180 | 181 | ## Other topics 182 | 183 | - [Internationalization](../docs/{{ app_name }}/internationalization.md) 184 | - [Security](../docs/{{ app_name }}/security.md) 185 | - [Image optimization](../docs/{{ app_name }}/image-optimization.md) 186 | - Refer to the [architecture decision records](../docs/decisions) for more context on technical decisions. 187 | -------------------------------------------------------------------------------- /template/{{app_name}}/docker-compose.yml.jinja: -------------------------------------------------------------------------------- 1 | # Local development compose file 2 | services: 3 | {{ app_name }}: 4 | build: 5 | context: . 6 | target: dev 7 | env_file: 8 | - ./.env 9 | # Add your non-secret environment variables to this file: 10 | - ./.env.development 11 | # If you have secrets, add them to this file and uncomment this line: 12 | # - ./.env.local 13 | volumes: 14 | - ./src:/app/src 15 | - ./public:/app/public 16 | restart: always 17 | ports: 18 | - {{ app_local_port }}:{{ app_local_port }} 19 | 20 | {{ app_name}}-storybook: 21 | build: 22 | context: ./ 23 | target: dev 24 | command: npm run storybook -- --no-open 25 | volumes: 26 | - ./src:/app/src 27 | - ./public:/app/public 28 | - ./.storybook:/app/.storybook 29 | - ./stories:/app/stories 30 | restart: always 31 | ports: 32 | - 6006:6006 33 | -------------------------------------------------------------------------------- /template/{{app_name}}/jest.config.js: -------------------------------------------------------------------------------- 1 | // See https://nextjs.org/docs/testing 2 | const nextJest = require("next/jest"); 3 | 4 | const createJestConfig = nextJest({ 5 | // Provide the path to your Next.js app to load next.config.js and .env files in your test environment 6 | dir: "./", 7 | }); 8 | 9 | // Add any custom config to be passed to Jest 10 | /** @type {import('jest').Config} */ 11 | const customJestConfig = { 12 | setupFilesAfterEnv: ["/tests/jest.setup.ts"], 13 | testEnvironment: "jsdom", 14 | // if using TypeScript with a baseUrl set to the root directory then you need the below for alias' to work 15 | moduleDirectories: ["node_modules", "/"], 16 | moduleNameMapper: { 17 | // Force uuid to resolve with the CJS entry point ↴ 18 | // See https://github.com/uuidjs/uuid/issues/451 19 | // This can be removed when @aws-sdk uses uuid v9+ ↴ 20 | // https://github.com/aws/aws-sdk-js-v3/issues/3964 21 | uuid: require.resolve("uuid"), 22 | }, 23 | }; 24 | 25 | module.exports = createJestConfig(customJestConfig); 26 | -------------------------------------------------------------------------------- /template/{{app_name}}/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. 6 | -------------------------------------------------------------------------------- /template/{{app_name}}/next.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | const withNextIntl = require("next-intl/plugin")("./src/i18n/server.ts"); 3 | const sassOptions = require("./scripts/sassOptions"); 4 | 5 | /** 6 | * Configure the base path for the app. Useful if you're deploying to a subdirectory (like GitHub Pages). 7 | * If this is defined, you'll need to set the base path anywhere you use relative paths, like in 8 | * ``, ``, or `` tags. Next.js handles this for you automatically in `` tags. 9 | * @see https://nextjs.org/docs/api-reference/next.config.js/basepath 10 | * @example "/test" results in "localhost:3000/test" as the index page for the app 11 | */ 12 | const basePath = process.env.NEXT_PUBLIC_BASE_PATH; 13 | const appSassOptions = sassOptions(basePath); 14 | 15 | /** @type {import('next').NextConfig} */ 16 | const nextConfig = { 17 | basePath, 18 | reactStrictMode: true, 19 | // Output only the necessary files for a deployment, excluding irrelevant node_modules 20 | // https://nextjs.org/docs/app/api-reference/next-config-js/output 21 | output: "standalone", 22 | sassOptions: appSassOptions, 23 | // Continue to support older browsers (ES5) 24 | transpilePackages: [ 25 | // https://github.com/trussworks/react-uswds/issues/2605 26 | "@trussworks/react-uswds", 27 | ], 28 | }; 29 | 30 | module.exports = withNextIntl(nextConfig); 31 | -------------------------------------------------------------------------------- /template/{{app_name}}/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "engines": { 6 | "node": "20.19.2", 7 | "npm": ">=10.0.0" 8 | }, 9 | "scripts": { 10 | "build": "next build", 11 | "dev": "next dev", 12 | "format": "prettier --write '**/*.{js,json,md,mdx,ts,tsx,scss,yaml,yml}'", 13 | "format-check": "prettier --check '**/*.{js,json,md,mdx,ts,tsx,scss,yaml,yml}'", 14 | "lint": "next lint --dir src --dir stories --dir .storybook --dir tests --dir scripts --dir app --dir lib --dir types", 15 | "lint-fix": "npm run lint -- --fix", 16 | "postinstall": "node ./scripts/postinstall.js", 17 | "start": "next start", 18 | "storybook": "storybook dev -p 6006", 19 | "storybook-build": "storybook build", 20 | "test": "jest --ci --coverage", 21 | "test-update": "jest --update-snapshot", 22 | "test-watch": "jest --watch", 23 | "ts:check": "tsc --noEmit" 24 | }, 25 | "dependencies": { 26 | "@trussworks/react-uswds": "^9.0.0", 27 | "@uswds/uswds": "3.8.1", 28 | "lodash": "^4.17.21", 29 | "next": "^15.1.4", 30 | "next-intl": "^3.2.1", 31 | "react": "^18.2.0", 32 | "react-dom": "^18.2.0", 33 | "server-only": "^0.0.1" 34 | }, 35 | "devDependencies": { 36 | "@ianvs/prettier-plugin-sort-imports": "^4.0.2", 37 | "@storybook/addon-essentials": "^8.0.0", 38 | "@storybook/nextjs": "^8.0.0", 39 | "@storybook/react": "^8.0.0", 40 | "@testing-library/dom": "^10.0.0", 41 | "@testing-library/jest-dom": "^6.0.0", 42 | "@testing-library/react": "^16.0.0", 43 | "@testing-library/user-event": "^14.4.3", 44 | "@types/jest": "^29.5.5", 45 | "@types/jest-axe": "^3.5.5", 46 | "@types/lodash": "^4.14.202", 47 | "@types/node": "^22.10.2", 48 | "@types/react": "^18.2.42", 49 | "@types/react-dom": "^18.2.17", 50 | "@typescript-eslint/eslint-plugin": "^8.18.1", 51 | "@typescript-eslint/parser": "^8.18.1", 52 | "eslint": "^9.0.0", 53 | "eslint-config-next": "^15.1.1", 54 | "eslint-config-prettier": "^9.0.0", 55 | "eslint-plugin-jest": "^28.0.0", 56 | "eslint-plugin-jest-dom": "^5.0.1", 57 | "eslint-plugin-storybook": "^0.11.0", 58 | "eslint-plugin-testing-library": "^7.0.0", 59 | "eslint-plugin-you-dont-need-lodash-underscore": "^6.13.0", 60 | "jest": "^29.5.0", 61 | "jest-axe": "^9.0.0", 62 | "jest-cli": "^29.5.0", 63 | "jest-environment-jsdom": "^29.5.0", 64 | "postcss": "^8.4.31", 65 | "postcss-loader": "^8.0.0", 66 | "postcss-preset-env": "^10.0.0", 67 | "prettier": "^3.4.2", 68 | "sass": "^1.77.0", 69 | "sass-loader": "^16.0.0", 70 | "storybook": "^8.6.14", 71 | "style-loader": "^4.0.0", 72 | "typescript": "^5.0.0" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /template/{{app_name}}/postcss.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Storybook needs this for adding vendor prefixes like Next.js 3 | * does out of the box. 4 | * https://github.com/storybookjs/storybook/issues/23234 5 | */ 6 | module.exports = { 7 | plugins: { 8 | autoprefixer: {}, 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /template/{{app_name}}/public/img/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /template/{{app_name}}/scripts/postinstall.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Postinstall script that copies the USWDS assets to the public folder, 3 | * so that the static assets, like images, can be loaded by the browser. 4 | * This runs after `npm install` 5 | */ 6 | // @ts-check 7 | const fs = require("fs"); 8 | const path = require("path"); 9 | 10 | const uswdsPath = path.resolve(__dirname, "../node_modules/@uswds/uswds/dist"); 11 | const publicPath = path.resolve(__dirname, "../public/uswds"); 12 | 13 | function copyDir(src, dest) { 14 | if (!fs.existsSync(dest)) { 15 | fs.mkdirSync(dest); 16 | } 17 | fs.readdirSync(src).forEach((file) => { 18 | const srcPath = path.join(src, file); 19 | const destPath = path.join(dest, file); 20 | if (fs.lstatSync(srcPath).isDirectory()) { 21 | copyDir(srcPath, destPath); 22 | } else { 23 | fs.copyFileSync(srcPath, destPath); 24 | } 25 | }); 26 | } 27 | 28 | console.log("Copying USWDS assets from", uswdsPath, "to", publicPath); 29 | copyDir(uswdsPath, publicPath); 30 | console.log("Copied USWDS assets"); 31 | -------------------------------------------------------------------------------- /template/{{app_name}}/scripts/sassOptions.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | const sass = require("sass"); 3 | 4 | /** 5 | * Configure Sass to load USWDS assets, and expose a Sass function for setting the 6 | * correct relative path to image or font assets, when the site is hosted from a subdirectory. 7 | */ 8 | function sassOptions(basePath = "") { 9 | return { 10 | includePaths: [ 11 | "./node_modules/@uswds", 12 | "./node_modules/@uswds/uswds/packages", 13 | ], 14 | functions: { 15 | "add-base-path($path)": (path) => { 16 | return new sass.SassString(`${basePath}${path.getValue()}`); 17 | }, 18 | }, 19 | }; 20 | } 21 | 22 | module.exports = sassOptions; 23 | -------------------------------------------------------------------------------- /template/{{app_name}}/src/app/[locale]/health/page.tsx: -------------------------------------------------------------------------------- 1 | export default function Page() { 2 | return <>healthy; 3 | } 4 | -------------------------------------------------------------------------------- /template/{{app_name}}/src/app/[locale]/layout.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Root layout component, wraps all pages. 3 | * @see https://nextjs.org/docs/app/api-reference/file-conventions/layout 4 | */ 5 | import { Metadata } from "next"; 6 | 7 | import Layout from "src/components/Layout"; 8 | 9 | import "src/styles/styles.scss"; 10 | 11 | export const metadata: Metadata = { 12 | icons: [`${process.env.NEXT_PUBLIC_BASE_PATH ?? ""}/img/logo.svg`], 13 | }; 14 | 15 | interface LayoutProps { 16 | children: React.ReactNode; 17 | params: Promise<{ locale: string }>; 18 | } 19 | 20 | export default async function RootLayout({ children, params }: LayoutProps) { 21 | const { locale } = await params; 22 | return ( 23 | 24 | 25 | {/* Separate layout component for the inner-body UI elements since Storybook 26 | and tests trip over the fact that this file renders an tag */} 27 | {children} 28 | 29 | 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /template/{{app_name}}/src/app/[locale]/page.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta } from "@storybook/react"; 2 | 3 | import { View } from "./view"; 4 | 5 | const meta: Meta = { 6 | title: "Pages/Home", 7 | component: View, 8 | }; 9 | export default meta; 10 | 11 | export const Preview = {}; 12 | -------------------------------------------------------------------------------- /template/{{app_name}}/src/app/[locale]/page.test.tsx: -------------------------------------------------------------------------------- 1 | import { axe } from "jest-axe"; 2 | import { render, screen } from "tests/react-utils"; 3 | 4 | import { View } from "./view"; 5 | 6 | describe("Index - View", () => { 7 | // Demonstration of rendering translated text, and asserting the presence of a dynamic value. 8 | // You can delete this test for your own project. 9 | it("renders link to Next.js docs", () => { 10 | render(); 11 | 12 | const link = screen.getByRole("link", { name: /next\.js/i }); 13 | 14 | expect(link).toBeInTheDocument(); 15 | expect(link).toHaveAttribute("href", "https://nextjs.org/docs"); 16 | expect(screen.getByText(/\$1,234/)).toBeInTheDocument(); 17 | }); 18 | 19 | it("passes accessibility scan", async () => { 20 | const { container } = render(); 21 | const results = await axe(container); 22 | 23 | expect(results).toHaveNoViolations(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /template/{{app_name}}/src/app/[locale]/page.tsx: -------------------------------------------------------------------------------- 1 | import { Metadata } from "next"; 2 | 3 | import { getTranslations } from "next-intl/server"; 4 | 5 | import { View } from "./view"; 6 | 7 | export async function generateMetadata({ 8 | params, 9 | }: { 10 | params: Promise<{ locale: string }>; 11 | }) { 12 | const { locale } = await params; 13 | const t = await getTranslations({ locale }); 14 | const meta: Metadata = { 15 | title: t("home.title"), 16 | }; 17 | 18 | return meta; 19 | } 20 | 21 | export default function Controller() { 22 | return ; 23 | } 24 | -------------------------------------------------------------------------------- /template/{{app_name}}/src/app/[locale]/view.tsx: -------------------------------------------------------------------------------- 1 | import { useTranslations } from "next-intl"; 2 | 3 | export function View() { 4 | const t = useTranslations("home"); 5 | 6 | return ( 7 | <> 8 |

{t("title")}

9 | 10 | {/* Demonstration of more complex translated strings, with safe-listed links HTML elements */} 11 |

12 | {t.rich("intro", { 13 | LinkToNextJs: (content) => ( 14 | {content} 15 | ), 16 | })} 17 |

18 |
19 | {t.rich("body", { 20 | ul: (content) =>
    {content}
, 21 | li: (content) =>
  • {content}
  • , 22 | })} 23 | 24 |

    25 | {/* Demonstration of formatters */} 26 | {t("formatting", { 27 | amount: 1234, 28 | isoDate: new Date("2023-11-29T23:30:00.000Z"), 29 | })} 30 |

    31 |
    32 | 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /template/{{app_name}}/src/app/api/hello/route.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Next.js route handler 3 | * @see https://nextjs.org/docs/app/building-your-application/routing/route-handlers 4 | */ 5 | import { NextRequest, NextResponse } from "next/server"; 6 | 7 | type Data = { 8 | name: string; 9 | user_id: string | null; 10 | }; 11 | 12 | export function GET(request: NextRequest) { 13 | const query = request.nextUrl.searchParams; 14 | const user_id = query.get("user_id"); 15 | 16 | return NextResponse.json({ name: "John Doe", user_id }); 17 | } 18 | -------------------------------------------------------------------------------- /template/{{app_name}}/src/components/Footer.tsx: -------------------------------------------------------------------------------- 1 | import { useTranslations } from "next-intl"; 2 | import { 3 | Address, 4 | FooterNav, 5 | Grid, 6 | GridContainer, 7 | Footer as USWDSFooter, 8 | } from "@trussworks/react-uswds"; 9 | 10 | const Footer = () => { 11 | const t = useTranslations("components.Footer"); 12 | 13 | return ( 14 | 18 | {t("return_to_top")} 19 | 20 | } 21 | primary={ 22 | 23 | 24 | 29 | 38 | Nav link 1 39 | , 40 | 45 | Nav link 2 46 | , 47 | ]} 48 | /> 49 | 50 | 51 |
    55 | (800) CALL-GOVT 56 | , 57 | 58 | info@agency.gov 59 | , 60 | ]} 61 | /> 62 | 63 | 64 | 65 | } 66 | secondary={

    {t("agency_name")}

    } 67 | /> 68 | ); 69 | }; 70 | 71 | export default Footer; 72 | -------------------------------------------------------------------------------- /template/{{app_name}}/src/components/Header.test.tsx: -------------------------------------------------------------------------------- 1 | import userEvent from "@testing-library/user-event"; 2 | import { render, screen } from "tests/react-utils"; 3 | 4 | import Header from "./Header"; 5 | 6 | describe("Header", () => { 7 | it("toggles the mobile nav menu", async () => { 8 | render(
    ); 9 | 10 | const menuButton = screen.getByRole("button", { name: "Menu" }); 11 | 12 | expect(menuButton).toBeInTheDocument(); 13 | 14 | await userEvent.click(menuButton); 15 | 16 | const closeButton = screen.getByRole("button", { name: /close/i }); 17 | 18 | expect(closeButton).toBeInTheDocument(); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /template/{{app_name}}/src/components/Header.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useTranslations } from "next-intl"; 4 | import { useState } from "react"; 5 | import { 6 | NavMenuButton, 7 | PrimaryNav, 8 | Title, 9 | Header as USWDSHeader, 10 | } from "@trussworks/react-uswds"; 11 | 12 | const primaryLinks = [ 13 | { 14 | i18nKey: "nav_link_home", 15 | href: "/", 16 | }, 17 | { 18 | i18nKey: "nav_link_health", 19 | href: "/health", 20 | }, 21 | ] as const; 22 | 23 | const Header = () => { 24 | const t = useTranslations("components.Header"); 25 | 26 | const [isMobileNavExpanded, setIsMobileNavExpanded] = useState(false); 27 | const handleMobileNavToggle = () => { 28 | setIsMobileNavExpanded(!isMobileNavExpanded); 29 | }; 30 | 31 | const navItems = primaryLinks.map((link) => ( 32 | 33 | {t(link.i18nKey)} 34 | 35 | )); 36 | 37 | return ( 38 | <> 39 |
    42 | 43 |
    44 |
    45 | 46 | <div className="display-flex flex-align-center"> 47 | <span className="margin-right-1"> 48 | <img 49 | className="width-3 desktop:width-5 text-bottom margin-right-05" 50 | src={`${ 51 | process.env.NEXT_PUBLIC_BASE_PATH ?? "" 52 | }/img/logo.svg`} 53 | alt="Site logo" 54 | /> 55 | </span> 56 | <span className="font-sans-lg flex-fill">{t("title")}</span> 57 | </div> 58 | 59 | 63 |
    64 | 69 |
    70 |
    71 | 72 | ); 73 | }; 74 | 75 | export default Header; 76 | -------------------------------------------------------------------------------- /template/{{app_name}}/src/components/Layout.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta } from "@storybook/react"; 2 | import { defaultLocale } from "src/i18n/config"; 3 | 4 | import Layout from "./Layout"; 5 | 6 | const meta: Meta = { 7 | component: Layout, 8 | }; 9 | export default meta; 10 | 11 | /** 12 | * Below is an example of using Storybook's `args` feature to render 13 | * different versions of the same component. 14 | * @see https://storybook.js.org/docs/react/writing-stories/args 15 | */ 16 | export const Preview1 = { 17 | args: { 18 | locale: defaultLocale, 19 | children:

    Page contents go here

    , 20 | }, 21 | }; 22 | 23 | export const Preview2 = { 24 | args: { 25 | ...Preview1.args, 26 | children: ( 27 | <> 28 |

    Another demo

    29 |

    This is an example of another story with a different arg.

    30 | 31 | ), 32 | }, 33 | }; 34 | -------------------------------------------------------------------------------- /template/{{app_name}}/src/components/Layout.test.tsx: -------------------------------------------------------------------------------- 1 | import { axe } from "jest-axe"; 2 | import { render, screen } from "tests/react-utils"; 3 | 4 | import Layout from "./Layout"; 5 | 6 | describe("Layout", () => { 7 | it("renders children in main section", () => { 8 | render( 9 | 10 |

    child

    11 |
    , 12 | ); 13 | 14 | const header = screen.getByRole("heading", { name: /child/i, level: 1 }); 15 | 16 | expect(header).toBeInTheDocument(); 17 | }); 18 | 19 | it("passes accessibility scan", async () => { 20 | const { container } = render( 21 | 22 |

    child

    23 |
    , 24 | ); 25 | const results = await axe(container); 26 | 27 | expect(results).toHaveNoViolations(); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /template/{{app_name}}/src/components/Layout.tsx: -------------------------------------------------------------------------------- 1 | import { pick } from "lodash"; 2 | 3 | import { 4 | NextIntlClientProvider, 5 | useMessages, 6 | useTranslations, 7 | } from "next-intl"; 8 | import { GovBanner, Grid, GridContainer } from "@trussworks/react-uswds"; 9 | 10 | import Footer from "./Footer"; 11 | import Header from "./Header"; 12 | 13 | type Props = { 14 | children: React.ReactNode; 15 | locale?: string; 16 | }; 17 | 18 | const Layout = ({ children, locale }: Props) => { 19 | const t = useTranslations("components.Layout"); 20 | const messages = useMessages(); 21 | 22 | return ( 23 | // Stick the footer to the bottom of the page 24 |
    25 | 26 | {t("skip_to_main")} 27 | 28 | 29 | 33 |
    34 | 35 | {/* grid-col-fill so that the footer sticks to the bottom of tall screens */} 36 |
    37 | 38 | 39 | {children} 40 | 41 | 42 |
    43 |
    44 |
    45 | ); 46 | }; 47 | 48 | export default Layout; 49 | -------------------------------------------------------------------------------- /template/{{app_name}}/src/i18n/config.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Shared i18n configuration for use across the server and client 3 | */ 4 | import type { getRequestConfig } from "next-intl/server"; 5 | 6 | type RequestConfig = Awaited< 7 | ReturnType[0]> 8 | >; 9 | 10 | /** 11 | * List of languages supported by the application. Other tools (Storybook, tests) reference this. 12 | * These must be BCP47 language tags: https://en.wikipedia.org/wiki/IETF_language_tag#List_of_common_primary_language_subtags 13 | */ 14 | export const locales = ["en-US", "es-US"] as const; 15 | export type Locale = (typeof locales)[number]; 16 | export const defaultLocale: Locale = "en-US"; 17 | 18 | /** 19 | * Specifying a time zone affects the rendering of dates and times. 20 | * When not defined, the time zone of the server runtime is used. 21 | * @see https://next-intl-docs.vercel.app/docs/usage/configuration#time-zone 22 | */ 23 | export const timeZone: RequestConfig["timeZone"] = "America/New_York"; 24 | 25 | /** 26 | * Define the default formatting for date, time, and numbers. 27 | * @see https://next-intl-docs.vercel.app/docs/usage/configuration#formats 28 | */ 29 | export const formats: RequestConfig["formats"] = { 30 | number: { 31 | currency: { 32 | currency: "USD", 33 | }, 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /template/{{app_name}}/src/i18n/getMessagesWithFallbacks.ts: -------------------------------------------------------------------------------- 1 | import { merge } from "lodash"; 2 | import { defaultLocale, Locale, locales } from "src/i18n/config"; 3 | 4 | interface LocaleFile { 5 | messages: Messages; 6 | } 7 | 8 | async function importMessages(locale: Locale) { 9 | const { messages } = (await import(`./messages/${locale}`)) as LocaleFile; 10 | return messages; 11 | } 12 | 13 | /** 14 | * Get all messages for the given locale. If any translations are missing 15 | * from the current locale, the missing key will fallback to the default locale 16 | */ 17 | export async function getMessagesWithFallbacks( 18 | requestedLocale: string = defaultLocale, 19 | ) { 20 | const isValidLocale = locales.includes(requestedLocale as Locale); // https://github.com/microsoft/TypeScript/issues/26255 21 | if (!isValidLocale) { 22 | console.error( 23 | "Unsupported locale was requested. Falling back to the default locale.", 24 | { locale: requestedLocale, defaultLocale }, 25 | ); 26 | requestedLocale = defaultLocale; 27 | } 28 | 29 | const targetLocale = requestedLocale as Locale; 30 | let messages = await importMessages(targetLocale); 31 | 32 | if (targetLocale !== defaultLocale) { 33 | const fallbackMessages = await importMessages(defaultLocale); 34 | messages = merge({}, fallbackMessages, messages); 35 | } 36 | 37 | return messages; 38 | } 39 | -------------------------------------------------------------------------------- /template/{{app_name}}/src/i18n/messages/en-US/index.ts: -------------------------------------------------------------------------------- 1 | export const messages = { 2 | components: { 3 | Header: { 4 | nav_link_home: "Home", 5 | nav_link_health: "Health", 6 | nav_menu_toggle: "Menu", 7 | title: "Site title", 8 | }, 9 | Footer: { 10 | agency_name: "Agency name", 11 | return_to_top: "Return to top", 12 | }, 13 | Layout: { 14 | skip_to_main: "Skip to main content", 15 | }, 16 | }, 17 | home: { 18 | title: "Home", 19 | intro: 20 | "This is a template for a React web application using the Next.js framework.", 21 | body: "This is template includes:
    • Framework for server-side rendered, static, or hybrid React applications
    • TypeScript and React testing tools
    • U.S. Web Design System for themeable styling and a set of common components
    • Type checking, linting, and code formatting tools
    • Storybook for a frontend workshop environment
    ", 22 | formatting: 23 | "The template includes an internationalization library with basic formatters built-in. Such as numbers: { amount, number, currency }, and dates: { isoDate, date, long}.", 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /template/{{app_name}}/src/i18n/messages/es-US/index.ts: -------------------------------------------------------------------------------- 1 | export const messages = { 2 | components: { 3 | Header: { 4 | title: "Título del sitio", 5 | }, 6 | }, 7 | home: { 8 | title: "Hogar", 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /template/{{app_name}}/src/i18n/server.ts: -------------------------------------------------------------------------------- 1 | import { getRequestConfig } from "next-intl/server"; 2 | 3 | import { formats, timeZone } from "./config"; 4 | import { getMessagesWithFallbacks } from "./getMessagesWithFallbacks"; 5 | 6 | /** 7 | * Make locale messages available to all server components. 8 | * This method is used behind the scenes by `next-intl/plugin`, which is setup in next.config.js. 9 | * @see https://next-intl-docs.vercel.app/docs/usage/configuration#nextconfigjs 10 | */ 11 | export default getRequestConfig(async ({ requestLocale }) => { 12 | const locale = await requestLocale; 13 | return { 14 | formats, 15 | messages: await getMessagesWithFallbacks(locale), 16 | timeZone, 17 | }; 18 | }); 19 | -------------------------------------------------------------------------------- /template/{{app_name}}/src/middleware.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Middleware allows you to run code before a request is completed. Then, based on the 3 | * incoming request, you can modify the response by rewriting, redirecting, modifying 4 | * the request or response headers, or responding directly. 5 | * @see https://nextjs.org/docs/app/building-your-application/routing/middleware 6 | */ 7 | import createIntlMiddleware from "next-intl/middleware"; 8 | import { NextRequest } from "next/server"; 9 | 10 | import { defaultLocale, locales } from "./i18n/config"; 11 | 12 | // Don't run middleware on API routes or Next.js build output 13 | export const config = { 14 | matcher: ["/((?!api|_next|.*\\..*).*)"], 15 | }; 16 | 17 | /** 18 | * Detect the user's preferred language and redirect to a localized route 19 | * if the preferred language isn't the current locale. 20 | */ 21 | const i18nMiddleware = createIntlMiddleware({ 22 | locales, 23 | defaultLocale, 24 | // Don't prefix the URL with the locale when the locale is the default locale (i.e. "en-US") 25 | localePrefix: "as-needed", 26 | }); 27 | 28 | export default function middleware(req: NextRequest) { 29 | const response = i18nMiddleware(req); 30 | return response; 31 | } 32 | -------------------------------------------------------------------------------- /template/{{app_name}}/src/styles/_uswds-theme-custom-styles.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * * * * * ============================== 3 | * * * * * ============================== 4 | * * * * * ============================== 5 | * * * * * ============================== 6 | ======================================== 7 | ======================================== 8 | ======================================== 9 | ---------------------------------------- 10 | USWDS THEME CUSTOM STYLES 11 | ---------------------------------------- 12 | !! Copy this file to your project's 13 | sass root. Don't edit the version 14 | in node_modules. 15 | ---------------------------------------- 16 | Custom project SASS goes here. 17 | 18 | i.e. 19 | @include u-padding-right('05'); 20 | ---------------------------------------- 21 | */ 22 | 23 | @use "uswds-core" as *; 24 | -------------------------------------------------------------------------------- /template/{{app_name}}/src/styles/_uswds-theme.scss: -------------------------------------------------------------------------------- 1 | /* 2 | ---------------------------------------- 3 | USWDS settings overrides 4 | ---------------------------------------- 5 | See https://designsystem.digital.gov/documentation/settings/ 6 | for a full list of available settings. 7 | ---------------------------------------- 8 | */ 9 | 10 | @use "uswds-core" with ( 11 | // Disable scary but mostly irrelevant warnings: 12 | $theme-show-notifications: false, 13 | 14 | // Specify USWDS font and image paths. 15 | // Same paths are successfully used for both Next.js and Storybook. 16 | $theme-font-path: add-base-path("/uswds/fonts"), 17 | $theme-image-path: add-base-path("/uswds/img"), 18 | // Ensure utility classes always override other styles 19 | $utilities-use-important: true, 20 | 21 | // Use USWDS defaults for typography 22 | $theme-style-body-element: true, 23 | $theme-font-type-sans: "public-sans", 24 | $theme-global-content-styles: true, 25 | $theme-global-link-styles: true, 26 | $theme-global-paragraph-styles: true 27 | ); 28 | -------------------------------------------------------------------------------- /template/{{app_name}}/src/styles/styles.scss: -------------------------------------------------------------------------------- 1 | @forward "uswds-theme"; 2 | @forward "uswds"; 3 | @forward "uswds-theme-custom-styles"; 4 | -------------------------------------------------------------------------------- /template/{{app_name}}/src/types/i18n.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Setup type safe message keys with `next-intl` 3 | * @see https://next-intl-docs.vercel.app/docs/workflows/typescript 4 | */ 5 | type Messages = typeof import("src/i18n/messages/en-US").messages; 6 | type IntlMessages = Messages; 7 | -------------------------------------------------------------------------------- /template/{{app_name}}/stories/typography.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, StoryObj } from "@storybook/react"; 2 | 3 | const meta: Meta = { 4 | title: "Core/Typography", 5 | }; 6 | 7 | export default meta; 8 | 9 | export const GlobalStyles: StoryObj = { 10 | render: () => ( 11 | <> 12 |

    Heading 1

    13 |

    Heading 2

    14 |

    Heading 3

    15 |

    Heading 4

    16 |
    Heading 5
    17 |
    Heading 6
    18 |

    Intro text

    19 |

    Paragraph text

    20 |

    21 | Link and{" "} 22 | 23 | External link 24 | 25 |

    26 |
      27 |
    • Unordered list item: A
    • 28 |
    • Unordered list item: B
    • 29 |
    30 |
      31 |
    1. Ordered list item
    2. 32 |
    3. Ordered list item
    4. 33 |
    34 | 35 | ), 36 | }; 37 | 38 | export const SizeUtilities: StoryObj = { 39 | render: () => ( 40 | <> 41 |

    H1 with smaller text size

    42 |

    H2 with larger text size

    43 |

    Extra extra small paragraph

    44 | 45 | ), 46 | }; 47 | 48 | export const FamilyUtilities: StoryObj = { 49 | render: () => ( 50 | <> 51 |

    Sans heading

    52 |

    Serif body

    53 | 54 | ), 55 | }; 56 | 57 | export const ResponsiveUtilities: StoryObj = { 58 | render: () => ( 59 | <> 60 |

    61 | Responsive heading 62 |
    63 | (lg on mobile, xl on tablet, 2xl on desktop) 64 |

    65 |

    66 | Responsive paragraph 67 |
    68 | (2xs on mobile, sm on tablet, md on desktop) 69 |

    70 | 71 | ), 72 | }; 73 | 74 | export const ColorUtilities: StoryObj = { 75 | render: () => ( 76 | <> 77 |

    Examples of text color utility classes (not comprehensive):

    78 |

    Base

    79 |

    Accent warm dark

    80 |

    Success dark

    81 |

    Error dark

    82 | 83 | ), 84 | }; 85 | -------------------------------------------------------------------------------- /template/{{app_name}}/tests/jest.setup.ts: -------------------------------------------------------------------------------- 1 | import "@testing-library/jest-dom"; 2 | 3 | import { toHaveNoViolations } from "jest-axe"; 4 | 5 | expect.extend(toHaveNoViolations); 6 | -------------------------------------------------------------------------------- /template/{{app_name}}/tests/react-utils.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Exposes all of @testing-library/react, with one exception: 3 | * the exported render function is wrapped in a custom wrapper so 4 | * tests render within a global context that includes i18n content 5 | * @see https://testing-library.com/docs/react-testing-library/setup#custom-render 6 | */ 7 | import { render as _render, RenderOptions } from "@testing-library/react"; 8 | import { defaultLocale, formats, timeZone } from "src/i18n/config"; 9 | import { messages } from "src/i18n/messages/en-US"; 10 | 11 | import { NextIntlClientProvider } from "next-intl"; 12 | 13 | /** 14 | * Wrapper component that provides global context to all tests. Notably, 15 | * it allows our tests to render content when using i18n translation methods. 16 | */ 17 | const GlobalProviders = ({ children }: { children: React.ReactNode }) => { 18 | return ( 19 | 25 | {children} 26 | 27 | ); 28 | }; 29 | 30 | // 1. Export everything in "@testing-library/react" as-is 31 | export * from "@testing-library/react"; 32 | 33 | // 2. Then override the "@testing-library/react" render method 34 | export function render( 35 | ui: React.ReactElement, 36 | options: Omit = {}, 37 | ) { 38 | return _render(ui, { wrapper: GlobalProviders, ...options }); 39 | } 40 | -------------------------------------------------------------------------------- /template/{{app_name}}/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "target": "es6", 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | // Prevent .js source files. We only want to use TypeScript to 17 | // support comprehensive type checking. 18 | "allowJs": false, 19 | "checkJs": false, 20 | // Help speed up type checking in larger applications 21 | "incremental": true, 22 | "plugins": [ 23 | { 24 | "name": "next" 25 | } 26 | ] 27 | }, 28 | "include": [ 29 | "next-env.d.ts", 30 | "scripts/**/*", 31 | "src/**/*", 32 | "stories/**/*", 33 | "tests/**/*", 34 | "__mocks__/**/*", 35 | ".storybook/**/*", 36 | ".next/types/**/*.ts" 37 | ], 38 | "exclude": ["node_modules"] 39 | } 40 | --------------------------------------------------------------------------------