Warwick Grigg
10/06/2021, 2:16 PMFrank
Frank
Warwick Grigg
10/06/2021, 4:42 PMFrank
NextjsSite
construct?Warwick Grigg
10/07/2021, 3:49 PMconst ContentBucket = new sst.Bucket(this, "ContentBucket");
new s3deploy.BucketDeployment(this, "DeployContentPostBucket", {
sources: [s3deploy.Source.asset("./content/bucket/blog")],
destinationBucket: ContentBucket.s3Bucket,
destinationKeyPrefix: "blog", // optional prefix in destination bucket
});
// Create a Next.js site
const site = new sst.NextjsSite(this, "Site", {
path: "frontend",
environment: {
// Pass the bucket details to our app
REGION: scope.region,
BUCKET_NAME: ContentBucket.bucketName,
},
});
// Allow the Next.js site to access the content bucket
site.attachPermissions([ContentBucket]);
There's a cut down version of my project at https://github.com/warwickgrigg/sst-nextjs-blogWarwick Grigg
10/13/2021, 12:05 PMContentBucket.bucketName
into the NextjsSite construct at build time. Should I split sst.Bucket
and sst.NextjsSite
into different sst apps or stacks, and if so how do I feed ContentBucket.bucketName
from one to the other in an automated way? If there's some documentation covering that, either in sst or cdk, that would be great.Frank
REGION
will be accessible inside getStaticProps
b/c it’s a constant. But BUCKET_NAME
is not available at build time. The actual value of BUCKET_NAME
is only resolved at deploy time.Frank
getPost()
call in ur Next.js app doesn’t know where to load the post from.Frank
const ContentBucket = new sst.Bucket(this, "ContentBucket");
const ssmParamName = scope.logicalPrefixedName("ContentBucketName");
const ssmBucketName = new ssm.StringParameter(stack, 'ContentBucketNameParameter', {
parameterName: ssmParamName,
stringValue: ContentBucket.bucketName,
});
const site = new sst.NextjsSite(this, "Site", {
path: "frontend",
environment: {
REGION: scope.region,
BUCKET_NAME: ssm.StringParameter.valueFromLookup(stack, ssmParamName),
},
});
Frank
ssm.StringParameter.valueFromLookup
makes an AWS SDK call to SSM to fetch the value, so the bucket name is available at build time.Frank
Warwick Grigg
10/14/2021, 9:34 AMSSM parameter not available in account nnnnnnn, region us-east-1: warwick-nextjs-blog-ContentBucketName
I had changed ssm.StringParameter(stack, 'ContentBucketNameParameter', ...
to ssm.StringParameter(this, 'ContentBucketNameParameter',
. Should I have split my sst code into two stacks?Frank
ssm.StringParameter.valueFromLookup(this, ssmParamName)
doesn’t exist yet b/c the SSM parameter hasn’t been created.Frank
const ContentBucket = new sst.Bucket(this, "ContentBucket");
const ssmParamName = scope.logicalPrefixedName("ContentBucketName");
const ssmBucketName = new ssm.StringParameter(stack, 'ContentBucketNameParameter', {
parameterName: ssmParamName,
stringValue: ContentBucket.bucketName,
});
2. In our index file (ie. lib/index.js
or stacks/index.js
) make a call to the SSM value using AWS SDK. If the value does not exist (ie. in the case of the first deployment), default the value to a “placeholder”.
3. Pass the value to the stack, and assign it to the environment
const site = new sst.NextjsSite(this, "Site", {
path: "frontend",
environment: {
REGION: scope.region,
BUCKET_NAME: props.VALUE_FROM_AWS_SDK,
},
});
Frank
Warwick Grigg
10/18/2021, 6:00 PM{bucketName: this.logicalPrefixedName("ContentBucket")}
. Then in getStaticPaths / getstaticProps, detect a first time deployment by testing existence of the bucket with the name the value of process.env.BUCKET_NAME
? 3) Is there any way to serialise two stacks (or apps) so the second stack (or app) "awaits" completion of the first?Devin
10/18/2021, 11:21 PMFrank
1) On detecting a first-time deployment is there any way I can “force” a redeploy?Not out of the box. You can write an ie. bash script to check if the SSM exists, and run
sst deploy
twice.
2) Would it work to just create the bucket like this:Yes, hardcoding the name will work in this case. Thought it’s always tricky to hard code bucket names b/c the they need to unique across all region/all users on AWS.{bucketName: this.logicalPrefixedName("ContentBucket")}
3) Is there any way to serialise two stacks (or apps) so the second stack (or app) “awaits” completion of the first?No, all stacks are built before hand. You can’t built stackA, deploy stackA, then build stackB.
Frank
Warwick Grigg
10/19/2021, 4:36 PMDevin
10/19/2021, 4:36 PMWarwick Grigg
10/20/2021, 9:55 PMDevin
10/20/2021, 10:30 PMDevin
10/20/2021, 11:02 PMprod
so I suspect I still have something to figure out with env
variables and passing everything aroundDevin
10/20/2021, 11:02 PMWarwick Grigg
10/21/2021, 10:32 AMWarwick Grigg
10/21/2021, 11:30 AMDevin
10/21/2021, 2:32 PMFrank
Warwick Grigg
10/22/2021, 3:36 PMWarwick Grigg
10/22/2021, 4:04 PMenv: {
BUCKET_NAME: process.env.BUCKET_NAME,
TEST_VAR: process.env.TEST_VAR,
},
Devin
10/22/2021, 4:09 PMWarwick Grigg
10/22/2021, 4:18 PMWarwick Grigg
10/22/2021, 5:48 PMFrank
Frank
Alistair Stead
10/29/2021, 9:03 AM{{ ENV_VAR }}
are replaced in the generated html and js. However the static files uploaded to S3 for the site do not seem to have this set.
Would any of the following suggestions work for making {{ ENV_VAR }}
available at build time would be:
• Suggestion 1: Two phase builds:
◦ Phase 1: build the site so generate the manifests for lambda and S3 storage.
◦ Run stack deploy
◦ Phase 2: build the site with generated pages with {{ ENV_VAR }}
populated and upload to the S3 bucket
• Suggestion 2: Lambda builder - Build the site locally to generate the manifests and deploy the stack. Add to the stack a Lambda that can build the site and deploy the static pages to S3. Post deploy completion trigger the lambda builder processWarwick Grigg
10/29/2021, 9:30 AMAlistair Stead
10/29/2021, 1:11 PMAlistair Stead
10/29/2021, 1:15 PMNEXT_PUBLIC_
prefix for them to be available to the client side JS after next build
has been run?Alistair Stead
10/29/2021, 1:16 PMenv
values used in a none API route or in _app
?Warwick Grigg
10/29/2021, 1:43 PMAlistair Stead
10/29/2021, 2:04 PMWarwick Grigg
10/29/2021, 2:35 PMDevin
10/30/2021, 8:11 PMAlistair Stead
10/31/2021, 1:55 PMFrank
Frank
Frank
Alistair Stead
11/02/2021, 9:47 AMDevin
11/02/2021, 2:17 PMlet me write something up with an example, and I will share it with you.Thanks Frank! I’m available to help however I can. Please let me know if there’s anything I can do.
Frank
Kevin Grimm
05/19/2022, 3:48 AMKevin Grimm
05/19/2022, 3:58 AMKevin Grimm
05/19/2022, 12:51 PMNEXT_PUBLIC_
env var through the NextJsSite
construct. But because the User Pool wasn't yet created, the User Pool Name wasn't accessible.
This is how I'm calling Amplify on the frontend:
javascript
// amplifyLib.js
import { Amplify } from "aws-amplify";
import awsExports from "src/aws-exports";
export function configureAmplify() {
return Amplify.configure({ ...awsExports, ssr: true });
}
// src/aws-exports.js
const awsExports = {
Auth: {
mandatorySignIn: false,
region: process.env.NEXT_PUBLIC_REGION,
userPoolId: process.env.NEXT_PUBLIC_USER_POOL_ID,
identityPoolId: process.env.NEXT_PUBLIC_IDENTITY_POOL_ID,
userPoolWebClientId: process.env.NEXT_PUBLIC_USER_POOL_CLIENT_ID,
}
};
export default awsExports;
// index.js
import { configureAmplify } from "src/common/libs/amplifyLib";
// ... more imports
configureAmplify();
I ended up adapting the solutions from Frank and Warwick in this thread this thread . I also referred to Warwick's GitHub which was very helpful! There wasn't much of a difference outside of creating an SSM parameter for the User Pool ID Name as opposed to the S3 bucket.
Relevant code for the backend:
javascript
// index.js
import { SSMClient, GetParameterCommand } from "@aws-sdk/client-ssm"
const getParameter = async (paramName, region) => {
const params = {
Name: paramName,
WithDecryption: false,
};
const ssmClient = new SSMClient({ region });
try {
const { Parameter: { Value }} = await ssmClient.send(
new GetParameterCommand(params)
);
return Value;
} catch (err) {
return undefined;
}
}
export default async function main(app) {
const ssmParamName = app.logicalPrefixedName("UserPoolIdName");
const userPoolIdName = await getParameter(ssmParamName, app.region);
console.log({ ssmParamName, userPoolIdName });
new MyStack(app, "my-stack", { ssmParamName, userPoolIdName });
}
// MyStack.js
import { StringParameter } from "aws-cdk-lib/aws-ssm";
export default class MyStack extends sst.Stack {
constructor(scope, id, props) {
super(scope, id, props);
const { ssmParamName, userPoolIdName = "" } = props;
console.log({ ssmParamName, userPoolIdName });
const auth = new sst.Auth(this, "Auth", {
cognito: {
userPool: {
signInAliases: {
email: true,
},
},
},
});
// Define SSM Parameter
new StringParameter(this, "UserPoolIdParameter", {
parameterName: ssmParamName,
stringValue: auth.cognitoUserPool.userPoolId
});
const site = new sst.NextjsSite(this, "ProdSite", {
path: "frontend",
customDomain: {
domainName:
scope.stage === "prod" ? "<http://productionSiteURL.io|productionSiteURL.io>" : `${scope.stage}.<http://productionSiteURL.io|productionSiteURL.io>`,
domainAlias: scope.stage === "prod" ? "<http://www.productionSiteURL.io|www.productionSiteURL.io>" : undefined,
cdk: {
certificate: Certificate.fromCertificateArn(
this,
"ProductionCert",
process.env.PRODUCTION_CERT_ARN
),
},
hostedZone: HostedZone.fromHostedZoneAttributes(
this,
"ProdZone",
{
hostedZoneId: process.env.HOSTED_ZONE_ID,
zoneName: process.env.ZONE_NAME,
}
),
},
environment: {
NEXT_PUBLIC_REGION: scope.region,
NEXT_PUBLIC_USER_POOL_ID: userPoolIdName,
NEXT_PUBLIC_USER_POOL_ARN: auth.cognitoUserPool.userPoolArn,
NEXT_PUBLIC_IDENTITY_POOL_ID: auth.cognitoCfnIdentityPool.ref,
NEXT_PUBLIC_USER_POOL_CLIENT_ID:
auth.cognitoUserPoolClient.userPoolClientId,
},
waitForInvalidation: false, // dev only
});
this.addOutputs({
MESSAGE: userPoolIdName
? ""
: "Rerun `sst deploy` to generate UserPoolID",
});
}
}
I ran npx sst start
once and indeed the UserPoolIdName
was undefined. Running again as suggested produced a valid UserPoolIdName
as the resource name was being pulled from SSM. Running npx sst deploy
ran successfully.
My sense is that exposing other User Pool properties as Next environment variables should throw an error similar to what I experienced with the UserPoolIdName
. These are also passed into the Amplify configuration, though perhaps the User Pool ID is all that is required for the initialization. I also don't have an immediate need for Auth, so haven't given the Identity pool or User pool client an opportunity to yell at me 😄
Similar to others, I suppose there must be a cleaner way to do this. Scott considers in this thread the idea of moving the frontend to a separate repo, which then fetches the vars from a config. Another thought would be to create a separate stack for SSM params, deploy that first, and then deploy the Main stack. Though expect these may require separate sst.json
files.